隔离测试是最佳实践

时间:2014-03-08 14:10:38

标签: ruby-on-rails rspec

我有一个Rails应用程序。说它是一个Rails应用程序,允许你护理一些动物,我们有一个动作,同时给许多动物一些食物。为此,我们有一个迭代每个动物并调用#eat方法的类。 eat方法是从州starvedsated的过渡。如果动物已经sated,则此转换失败。

示例:

class Animal < ActiveRecord::Base
  state_machine :state do
    state :starved
    state :sated

    event :eat do
      transition starved: :sated
    end
  end
end

class EatingService
  attr_reader :error_models, :models

  def new(models)
    @error_models = []
    @models       = models
  end

  def process
    ActiveRecord::Base.transaction do
      models.each { |model| @error_models << model unless model.eat }
      raise ActiveRecord::Rollback unless successfully_completed?
    end

    successfully_completed?
  end

  def successfully_completed?
    error_models.empty?
  end
end

在添加事务之前,我可以使用模拟对象轻松测试它。

现在,我知道我不应该使用我的DogCat类,因为EatingService类没有绑定任何类但是如何测试回滚是否运行良好在虚拟对象上?

PS:在这个例子中,我只讨论Animal,但在实际应用程序中,我使用“EatingService”完全不同类型的类,而不仅仅是动物或这些继承的类。

3 个答案:

答案 0 :(得分:0)

在我看来,这样的设计打破了OOP原则“告诉,不要问”,因此很难将测试分开。

饮食服务承担了太多不属于他们的责任。饥饿与否不是这个班级应该关心的。所有需要做的服务就是要求动物进食。至于吃还是不吃,这就是动物的事。

我建议如下逻辑,将判断移至Animal。

# Eating service
def process
  food = prepare_food
  @animal.eat(food)
end

# Animal
def eat(food)
  return false unless is_hungry? || like?(food) 
  chew(foo)
end

所有动物需要做的是回应方法eat,这很容易被嘲笑。而Serice的工作就是送动物吃。

答案 1 :(得分:0)

注释掉所有代码。然后编写一个测试文件,因为你的一行不存在。重复,直到你有代码。不要先欺骗并编写代码。

您的测试应该使用他们需要的任何对象。 “隔离”并不意味着对目标类A的测试无法在B类中发现错误。测试隔离只意味着测试通过或失败取决于可能的最少因素,包括其他测试,包括其他目标类。

尽量不要使用嘲笑。我已经看到尽管有大约1000个测试用例,项目速度变慢,因为测试滥用了模拟并经常忽略测试实际代码。

答案 2 :(得分:0)

您可以检查是否已抛出ActiveRecord::Rollback

it "fails if someone is sated" do
  allow(ActiveRecord::Base).to receive(:transaction).and_yield
  allow(subject.models[1]).to receive(:eat).and_return(false)

  expect { subject.process }.to raise_error ActiveRecord::Rollback
end