我应该使用纯单元测试还是集成测试来测试命令模式?

时间:2014-05-22 20:44:31

标签: ruby-on-rails unit-testing rspec integration-testing command-pattern

命令模式是一种避免胖控制器和胖模型的好方法,其中几个组件必须协同工作。这是实现业务逻辑的地方。 问题是我不确定'纯'单元测试在这里是否有意义,或者更好地编写集成测试。举个例子:

class MyCommand
  def initialize(attributes)
    @attributes = attributes
  end

  def execute
    objA = ClassA.find(@attributes[:class_a_id])
    objB = ClassB.find(@attributes[:class_b_id])

    objA.do_something
    objB.do_something_with_a(objA)

    objA.save
    objB.save
  end
end

请注意,这是一个例子。当然,我们可以编写一个更好的命令,但这个例子很好。

我们可以看到,objA和objB在命令中被实例化。我可以使用DI来避免这种内部依赖,但如果我这样做,实例化将在使用此命令的控制器内完成,这正是我不想要的。

如果我为此编写单元测试,我将不得不存根ClassA.find和ClassB.find方法,并检查是否调用了do_something和do_something_with_a。此外,我们还要检查是否也调用了save。这里有太多的残留和非常脆弱的测试。

我们可以通过使用FactoryGirl来解决这个问题

...
before do
  FactoryGirl.create(:class_a)
  FactoryGirl.create(:class_b)
end
...

然后,我们不需要存根ActiveRecord查找方法。

为避免在保存时预期的方法调用,我们可以检查数据库是否已正确更新。

如果我们选择第二种方法,我们正在编写一个集成测试,涉及ClassA,ClassB,MyCommand和数据库之间的协作。实际上这就像是一个功能测试,因为我们正在测试一部分功能逻辑(就像我们使用控制器而不管框架一样)。

所以,问题是,为命令编写纯单元测试是否值得?

提前致谢

1 个答案:

答案 0 :(得分:2)

答案取决于你如何设计真实的命令。

在您的示例中,Command将其大部分工作委托给ClassA和ClassB。如果每个Command只查找一些对象,调用它们的大多数工作的模型方法并保存它们,你应该编写纯单元测试 - 你不想在测试中复制模型方法的测试命令。由于没有很多方法调用,因此编写纯单元测试并不困难。您可以将加载/调用/保存模式抽象为Command超类并仅测试一次(或者在您的命令测试中共享的方法)。更好的是,如果你已经有验收测试,你可能根本不需要测试这样的命令 - 它们可能已经被验收测试完全覆盖了。

但如果您的命令只是委托给您的模型,那么您将不会减少您的模型。所有真正的代码仍然在您的模型中。如果Command调用仅在该Command中使用的方法,请将该方法移动到Command。如果这样做会给方法提供太多的功能Envy,请提取模型所包含的模块,其目的只是为了支持Command。

无论哪种方式,您都会将大量的模型代码移动到命令和Command支持模块中。该代码与模型代码非常相似,根据我的经验,最简单的测试方法与测试模型代码的方式相同,编写测试(技术集成测试),加载数据库对象并断言代码对它们的作用,而不是而不是嘲笑。

总的来说,我发现当Rails应用程序开始在模型和控制器(我喜欢外观或业务流程控制器,我自己)之间增长一层时,使用数据库对象测试该层,就像模型层一样好吧,然而用我测试控制器层的模拟试验,就像你说的那样,是痛苦和脆弱的。