我相信有一些最佳实践可以指导我们考虑何时使用模拟(在本例中为“双打”)与针对实际依赖项进行集成(在本例中为“工厂”)。有一本关于测试的非常好的书(警告:它使用 Java 示例)描述了测试驱动开发的目的,我认为它对讨论 Rails 应用程序中的测试很有帮助。它描述了测试的意图如下:
...我们在测试驱动开发中的意图是使用模拟对象来显示对象之间的关系。
弗里曼,史蒂夫;普赖斯,纳特(2009-10-12)。以测试为指导的不断发展的面向对象软件(Kindle Locations 3878-3879)。培生教育(美国)。Kindle版。
如果我们考虑强调使用测试驱动开发不仅是为了防止我们引入回归,而且是为了帮助我们思考我们的代码是如何根据其接口和与其他对象的关系来构造的,我们自然会在许多情况下使用模拟. 我将在下面描述这如何适用于您的具体问题。
首先,关于我们在模型测试中是使用模拟对象还是真正的依赖关系——如果我们正在测试类 Foo 及其对 Bar 的依赖关系,我们可能希望用模拟对象代替 Bar。通过这种方式,我们将清楚地看到与 Bar 的耦合级别,因为我们必须模拟将在其上调用的方法。如果我们发现我们的 Bar 模拟很复杂,这表明我们或许应该重构 Foo 和 Bar 以减少它们之间的耦合。
从某种意义上说,两者都Factory.create
具有Factory.build_stubbed
相同的效果,可以防止您显式地依赖相关类,我认为它们都一样臭,Factory.create 是两个选项中较慢的。
在我的测试中,我倾向于不太担心在控制器中模拟外部依赖项。我知道这比完全模拟运行起来要慢,而且你没有使控制器与模型显式耦合的好处,但是编写测试更快,而且我通常不担心明确控制器与其管理的持久记录之间的关系。只要你遵循“瘦控制器”的模式,这里就不应该担心太多的逻辑。如果我们需要在这里指定“测试气味”的级别,我会说它比依赖于其他工厂的模型测试要少一些。
我倾向于最不担心依赖于他们装饰的类的工厂的装饰器。这是因为根据定义,装饰器应该与它们所装饰的类保持相同的接口。装饰通常通过某种形式的继承来实现,无论是使用method_missing
委托给被装饰者,或者通过被装饰者的显式子类化。正因为如此,如果装饰器与它所装饰的东西的接口偏离太多,你就会打破其他良好的面向对象编程规则,比如 Liskov Substitution。只要你的装饰没有通过打破良好继承的规则来糟糕地实现,与你装饰的类的耦合就已经存在,所以如果你对被装饰者的测试依赖于持久化或存根,这不会让事情变得更糟它装饰的东西的工厂。您可以在装饰器测试中对工厂发疯,这并不重要 IMO。
我认为重要的是要注意,即使您在大多数情况下更喜欢模拟,您仍然应该有一些使用真实依赖项的集成测试。您会发现这些涵盖了特定的高价值案例,其中独立的单元测试提供了对您的类提供的功能的更多覆盖。
无论如何,我有时会违反上述所有规则,它们只是我在编写测试时使用的一些指导方针。我期待听到其他人在他们的测试中如何使用工厂(build_stubbed 和真正持久化)与模拟对象(双打)。