我已阅读过几本关于TDD和BDD的书和文章,应该避免在单个单元测试或规范中出现多个断言或期望。我可以理解这样做的原因。我仍然不确定什么是验证复杂结果的好方法。
假设测试中的方法返回一个复杂的对象(例如反序列化或数据库读取),我该如何正确验证结果?
1.在每个房产上发表声明:
Assert.AreEqual(result.Property1, 1);
Assert.AreEqual(result.Property2, "2");
Assert.AreEqual(result.Property3, null);
Assert.AreEqual(result.Property4, 4.0);
2.依赖正确实现的.Equals():
Assert.AreEqual(result, expectedResult);
1.的缺点是,如果第一个断言失败,则不会运行以下所有断言,这可能包含有价值的信息来查找问题。随着Properties的出现,可维护性也可能成为一个问题。
2.的缺点是我似乎在测试中不止一件事。如果.Equals()没有正确实现,我可能会得到误报或否定。还有2.我没有看到,如果测试失败,哪些属性实际上是不同的,但我认为通常可以用一个体面的.ToString()覆盖来解决。在任何情况下,我认为我应该避免被迫在失败的测试中抛出调试器以查看差异。我应该马上看到它。
2.下一个问题是它比较整个对象,即使某些测试只有某些属性可能很重要。
在TDD和BDD中,这将是一个不错的方式或最佳实践。
答案 0 :(得分:2)
不要从字面上理解TDD建议。 “好人”的意思是你应该每次测试测试一件事(为了避免因多种原因导致测试失败,随后必须调试测试才能找到原因)。 现在测试“一件事”意味着“一种行为”;每次测试都没有一个断言恕我直言。
这是一条指导方针而非规则。
所以选项: 用于比较整个数据值对象
为了比较对象的非结构化部分(任意属性集 - 这应该是罕见的),
答案 1 :(得分:0)
我会默认使用第二种方法。您是对的,如果Equals()
未正确实施,则会失败,但如果您已实施自定义Equals()
,则应对其进行单元测试。
第二种方法实际上更抽象,更简洁,允许您以后更容易修改代码,允许以相同的方式减少代码重复。假设您选择第一种方法:
Equals()
会更容易。当然,您仍然需要在预期结果中添加属性值(如果它不是默认值),但它比添加新断言更短。此外, 更容易看到第二种方法实际上有哪些属性不同。您只需在调试模式下运行测试,并比较break的属性。
顺便说一句,你永远不应该使用ToString()
。我想你想说[DebuggerDisplay]
属性?
2.下一个问题是它比较整个对象,即使对于某些测试,只有一些属性可能很重要。
如果您只需要比较一些属性,那么:
Cat
与另一个Cat
进行比较,但只考虑Dog
和其他动物共有的属性,请实施Cat : Animal
并比较基类。答案 2 :(得分:0)
根据问题中的上下文,我会选择选项1。
这可能取决于背景。如果我在.NET框架中使用某种内置对象序列化,我可以合理地确保如果没有遇到错误,那么整个对象都被适当地封送。在这种情况下,在对象中声明单个字段可能很好。我相信MS库可以做正确的事。
如果您正在使用SQL并手动将结果映射到域对象,我觉得选项1可以更快地诊断何时出现中断而不是选项2.选项2可能依赖于toString
方法来呈现断言失败:
Expected <1 2 null 4.0> but was <1 2 null null>
现在我被困在试图弄清楚4.0 / null是什么字段。当然,我可以将字段名称放入方法中:
Expected <Property1: 1, Property2: 2, Property3: null, Property4: 4.0> but was <Property1: 1, Property2: 2, Property3: null, Property4: null>
这适用于少量属性,但由于包装等原因而开始分解大量属性。此外,toString
维护可能会成为一个问题,因为它需要以与equals
方法。
当然没有正确的答案,在一天结束时,它真的归结为你的团队(或你自己)的个人偏好。
希望有所帮助!
布兰登
答案 3 :(得分:0)
尝试“每次测试行为的一个方面”而不是“每次测试一次断言”。如果您需要多个断言来说明您感兴趣的行为,请执行此操作。
例如,您的示例可能是ShouldHaveSensibleDefaults
。将其拆分为ShouldHaveADefaultNameAsEmptyString
,ShouldHaveNullAddress
,ShouldHaveAQuantityOfZero
等将不会清楚地解读。也不会将合理的默认值隐藏在另一个对象中,然后进行比较。
但是,我将分开这些示例,其中值具有默认值,其中包含从某个逻辑某处派生的任何属性,例如ShouldCalculateTheTotalQuantity
。将这样的小例子移到他们自己的方法中会使它更具可读性。
您可能还会发现对象的不同属性会因不同的上下文而更改。调出每个上下文并分别查看这些属性有助于我了解上下文与结果的关系。
提出“每次测试一个断言”的Dave Astels现在也使用了短语"one aspect of behavior",尽管他仍然发现它对separate that behavior有用。我倾向于在可读性和可维护性方面犯错,所以如果有一个以上断言的实用意义,我会这样做。