隔离测试轨道的最佳实践



有一个Rails应用程序。说这是一个Rails应用程序,允许你喂养一些动物,我们有一个行动,给一些食物,许多动物在同一时间。要做到这一点,我们有一个类迭代每个动物并调用#eat方法。eat法是由starved状态向sated状态的过渡。如果动物已经是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"有完全不同类型的类,不仅仅是动物或这些继承类。

在我看来,这样的设计打破了OOP原则"告诉,不要问",所以很难隔离测试。

餐饮服务承担了太多本不属于他们的责任。不管饿不饿都不是这个班级应该关心的。所有的服务需要做的就是让动物吃东西。至于吃不吃,那是动物的事。

我建议按照下面的逻辑,把判断移到动物身上。

# 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方法,这很容易被嘲笑。服务的工作结束于送动物吃。

注释掉所有代码。然后编写一个测试文件,因为其中一行不在那里。重复,直到你有了代码。不要作弊,先写代码。

并且您的测试应该使用他们需要的任何对象。"隔离"并不意味着在目标类a上的测试不能发现类b中的错误。测试隔离只是意味着测试的通过或失败取决于尽可能少的因素,包括其他测试和其他目标类。

尽量不要使用mock。我曾见过尽管有超过1000个测试用例,但项目还是变慢了,因为测试滥用模拟,而且经常忽略测试实际的代码。

您可以检查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

相关内容

  • 没有找到相关文章

最新更新