我目前的团队中经验最丰富的开发人员已根据我们应遵循的最佳实践制定了一些硬性规则。其中包括“您永远不会模拟域对象”。我问他为什么不能,但他从来没有时间给我适当的答案。现在他已经离开了一个星期,而我对此规则的不信任达到了顶峰,这就是我的情况。
我的域对象有一个带有多个参数的Update
方法,一个是计算器的接口。然后,它会更新一些字段,运行计算器并将其结果分配给其他字段。
Update
方法本身的正确单元测试相当长。
现在,我有一些代码可以执行一些操作,然后在这样的域对象上调用Update
。
我通常会模拟对象,只是检查Update
方法是否被正确的参数调用。但是现在,我必须测试它是否被正确调用,方法是检查它的字段并模拟计算器,然后像对单元格Update
方法本身进行测试一样。而且我将不得不在调用Update
方法的任何地方执行此操作。
Update
方法稍作更改,这些测试中的每一个都突然中断并需要重构,那会是多么有趣……我觉得这条规则就像把自己开枪打死。
所以我需要知道,为什么您从不模拟域对象?”
答案 0 :(得分:2)
您永远不会模拟域对象
与其说是最佳实践,不如说是一种启发式方法。我可以想象在某些项目中这是有意义的。
这种启发式方法可以帮助进行更好的测试。好的测试易于编写,易于阅读,一旦出现问题,它们将立即中断。如果您模拟域模型,则可能还必须模拟其行为。而且,如果您的模型中有一些复杂的行为,那么对其进行模拟将非常耗时。测试也将更加脆弱(在预测域模型输出时可能会发生错误)。
另一种选择是进行社交单元测试。这意味着要测试的单元将不是孤立的单个类,而是一个类及其某些依赖项。换句话说,您只需要在测试中使用域模型的具体对象即可。
我通常会模拟对象,只是检查是否使用正确的参数调用了Update方法。但是现在,我必须通过检查其字段并模拟计算器来测试它是否被正确调用,就像在单元测试Update方法本身时一样。
检查是否正在调用update方法不一定意味着测试成功。这可能不是您要检查的服务是否正常工作的内容。如果您通过了模型的具体类别(包括计算器),则无需进行任何无用的检查或重复其他测试中所做的事情。
此解决方案与单独测试类之间的区别之一是,当域模型中存在错误时,许多其他测试将失败。但这在我看来完全可以,因为无论如何都必须将其修复。
答案 1 :(得分:2)
您永远不会模拟域对象
避免模拟域对象的原因可能有多种:
与ORM相同,模拟库可能会影响域对象的设计。它们通常需要C#中的接口或虚拟方法。
用于对真实实例进行测试的偏好。人们经常使用模拟库,因为这是创建伪造品或存根的简单方法。通常,您可以构建域对象的实例并在测试中使用它。通过状态更改可以很容易地检查方法Update
被调用的事实。
我通常会模拟对象,只是检查是否使用正确的参数调用了Update方法。但是现在,我必须通过检查其字段并模拟计算器来测试它是否被正确调用,就像在单元测试Update方法本身时一样。
Update
方法稍作更改,这些测试中的每一个突然中断并需要重构...
评论中已经提到可能Update
方法要承担许多责任,这就是为什么在许多用例中都使用它的原因。
在定义被测系统(测试)时会有不同的口味。如果您倾向于测试类,那么您通常会使用模拟库。如果您倾向于测试场景或行为,那么您很可能只在乎状态更改。
您可能会为域对象探索不同的设计:尝试将Update
方法拆分为明确的特定于域的方法,以便每个客户端将触发不同的行为。使用这种方法,Update
方法的“合理较长”的测试甚至可能会过时,因为它将由其他测试进行测试。
答案 2 :(得分:1)
虽然Update()
的定制方法可能不如您从丰富域模型中预期的那样合适-这可能会影响测试的脆弱性-我同意您应该“不做X”建议盐。
如果您了解所有含义,并且知道集成测试版本的维护性较差,请绝对使用模拟程序。您是最终的判断者,而不是一味的声明或狂热的货真价实的判断者。