我有一个使用命令模式的类按顺序执行一系列简单的转换步骤。数据作为数据馈送(以XML格式)传入,然后使用单一用途的步骤类通过多个步骤进行转换。所以它可能看起来像这样(实际的类名不同):
raw_data = Downloader.new(feed)
parsed_data = Parser.new(raw_data)
translated_data = Translator.new(parsed_data)
sifted_data = Sifter.new(translated_data)
collate_data = Collator.new(sifted_data)
等
我为每个类都进行了单元测试,并且我有集成测试来验证完整的流程,包括调用每个类。
但我没有办法测试他们被称为的顺序
我喜欢一些测试,所以我知道:首先调用Downloader,然后是Parser,然后是Translator等。
这是Ruby和Rspec 3。
我确实找到了这个:http://testpractices.blogspot.com/2008/07/ordered-method-testing-with-rspec.html但是这是从2008年开始的,它也非常难看。有没有更好的方法来测试方法执行顺序?
谢谢!
答案 0 :(得分:13)
RSpec Mocks至少提供ordered
,因为至少RSpec 3.0:
您可以使用
ordered
来约束多个邮件预期的顺序。这通常不建议使用,因为在大多数情况下,顺序无关紧要,使用有序会使您的规格变得脆弱,但它偶尔会有用。当您使用ordered时,只有在按照声明的顺序接收消息时,该示例才会通过。
请注意,RSpec同意@spickermann认为这不是推荐的做法。但是,在某些情况下有必要,特别是在处理遗留代码时。
这是RSpec传递的例子:
RSpec.describe "Constraining order" do
it "passes when the messages are received in declared order" do
collaborator_1 = double("Collaborator 1")
collaborator_2 = double("Collaborator 2")
expect(collaborator_1).to receive(:step_1).ordered
expect(collaborator_2).to receive(:step_2).ordered
expect(collaborator_1).to receive(:step_3).ordered
collaborator_1.step_1
collaborator_2.step_2
collaborator_1.step_3
end
end
失败的例子:
RSpec.describe "Constraining order" do
it "fails when messages are received out of order on one collaborator" do
collaborator_1 = double("Collaborator 1")
expect(collaborator_1).to receive(:step_1).ordered
expect(collaborator_1).to receive(:step_2).ordered
collaborator_1.step_2
collaborator_1.step_1
end
it "fails when messages are received out of order between collaborators" do
collaborator_1 = double("Collaborator 1")
collaborator_2 = double("Collaborator 2")
expect(collaborator_1).to receive(:step_1).ordered
expect(collaborator_2).to receive(:step_2).ordered
collaborator_2.step_2
collaborator_1.step_1
end
end
答案 1 :(得分:2)
我认为方法调用的顺序并不重要,不应该进行测试。重要的是方法的结果,而不是其内部结构。测试内部方法调用的顺序(而不仅仅是测试方法的结果)将使以后重构方法变得更加困难。
但是如果仍然想测试顺序,那么你可能想要测试方法是用前面调用的方法的模拟结果调用的:
let(:raw_data) { double(:raw_data) }
let(:parsed_data) { double(:parsed_data) }
# ...
before do
allow(Downloader).to_receive(:new).and_return(raw_data)
allow(Parser).to_receive(:new).and_return(parsed_data)
# ...
end
it 'calls method in the right order' do
foo.bar # the method you want to test
expect(Downloader).to have_received(:new).with(feed)
expect(Parser).to have_received(:new).with(raw_data)
# ...
end
答案 2 :(得分:0)
我还要指出,一般来说,您不应该测试执行顺序,但是存在合理的应用情况。考虑一下涉及并发性的测试代码,例如Rails中的以下后台作业:
class MyJob < ActiveJob::Base
def perform
some_data.each do |item|
create(item, parent: parent)
item.do_something
item.processed = true
CleanUpJob.perform_later(item)
end
end
end
class CleanUpJob < ActiveJob::Base
def perform
# this can return true without waiting for each item to be created
return unless item.parent.items.all?(&:processed?)
# this also removes the parent!
item.destroy
end
end
在这里,您应该确保在创建每个项目之后改为执行CleanUpJob
,就像这样:
class MyJob < ActiveJob::Base
def perform
items = []
some_data.each do |item|
create(item, parent: parent)
item.do_something
item.processed = true
items.push(item)
end
items.each { |item| CleanUpJob.perform_later(item) }
end
end
对于不支持ordered
的框架(或较旧的版本),您可以做的是停止在稍后需要执行的调用上执行,并验证是否发生了先前的所有调用:
it "creates all items before scheduling cleanup" do
expect(CleanUpJob).to receive(:perform_later).and_rasie("let's stop here")
expect do
MyJob.perform
end.to raise_error(/let's stop here/)
expect(items.count).to eq(some_data.count)
end
同样,这是为特殊情况而设计,并非临时使用。