我有一个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
在添加事务之前,我可以使用模拟对象轻松测试它。
现在,我知道我不应该使用我的Dog
或Cat
类,因为EatingService
类没有绑定任何类但是如何测试回滚是否运行良好在虚拟对象上?
PS:在这个例子中,我只讨论Animal
,但在实际应用程序中,我使用“EatingService
”完全不同类型的类,而不仅仅是动物或这些继承的类。
答案 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