按合同设计,编写测试友好的代码,对象构造和依赖注入将所有最佳实践结合在一起

2023-11-24

我一直在试图找出编写测试友好代码的最佳实践,但更具体地说是与对象构造相关的实践。在蓝皮书中,我们发现我们应该在创建对象时强制执行不变量,以避免实体、值对象等的损坏。考虑到这一点,契约式设计似乎是避免对象损坏的解决方案,但是当我们遵循这个,我们最终可以编写这样的代码:

class Car
{
   //Constructor
   public Car(Door door, Engine engine, Wheel wheel)
   {
      Contract.Requires(door).IsNotNull("Door is required");
      Contract.Requires(engine).IsNotNull("Engine is required");
      Contract.Requires(wheel).IsNotNull("Wheel is required");
      ....
   }
   ...
   public void StartEngine()
   {
      this.engine.Start();
   }
}

嗯,第一眼看上去不错吧?看来我们正在构建一个安全的类,公开所需的合同,因此每次Car对象创建后我们可以确定该对象是“有效的”。

现在让我们从测试驱动的角度来看这个例子。

我想构建测试友好的代码,但为了能够单独测试我的Car我需要为每个依赖项创建一个模拟存根或虚拟对象来创建我的对象,即使我可能只想测试只使用这些依赖项之一的方法,例如StartEngine方法。遵循 Misko Hevery 的测试哲学,我想编写测试,明确指定我不关心 Door 或 Wheel 对象,只是将 null 引用传递给构造函数,但由于我正在检查 null,所以我无法做到这一点

这只是一小段代码,但是当您面对真正的应用程序时,编写测试变得越来越困难,因为您必须解决主题的依赖关系

Misko 提出,我们不应该在代码中滥用空检查(这与契约式设计相矛盾),因为这样做,编写测试会变得很痛苦,作为替代方案,他说最好编写更多的测试,而不是“只是幻想我们的代码是安全的,只是因为我们到处都有空检查”

您对此有何看法? 你会怎么做? 最佳实践应该是什么?


我需要为每个依赖项创建一个模拟存根或虚拟对象

这是人们常说的。但我认为这是错误的。如果一个Car与一个相关联Engine对象,为什么不使用real Engine单元测试时的对象Car class?

但是,有人会宣称,如果你这样做,你就不是unit测试你的代码;你的测试取决于Car类和Engine类:两个单元,因此是集成测试而不是单元测试。但那些人嘲笑吗?String也上课?或者HashSet<String>?当然不是。单元测试和集成测试之间的界限并不那么清晰。

更哲学地说,你can not在许多情况下创建良好的模拟对象。原因是,对于大多数方法来说,对象委托给关联对象的方式是不明确的。是否进行委托以及如何委托,由合同作为实现细节保留。唯一的要求是,在委托时,该方法满足其委托的先决条件。在这种情况下,只有一个功能齐全(非模拟)代表就可以了。如果真实对象检查其前提条件,则无法满足委托的前提条件将导致测试失败。并且调试测试失败也会很容易。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

按合同设计,编写测试友好的代码,对象构造和依赖注入将所有最佳实践结合在一起 的相关文章

随机推荐