I'd like to know about idioms or best practices for testing a multi-step workflow using rspec.
我想知道使用rspec测试多步骤工作流的习语或最佳实践。
Let's take as an example a "shopping cart" system, where the buying process might be
让我们以购物过程可能是“购物车”系统为例
- when user submits to basket and we are not using https, redirect to https
- when user submits to basket and we are using https and there is no cookie, create and display a new basket and send back a cookie
- when user submits to basket and we are using https and there is a valid cookie and the new item is for a different product than the first item, add a line to the basket and display both lines
- when user submits to basket and we are using https and there is a valid cookie and the new item is for the same product as a previous one, increment that basket line's quantity and display both lines
- when user clicks 'checkout' on the basket page and is using https and there is a cookie and the basket is non-empty and ...
- ...
当用户提交到购物篮并且我们没有使用https时,重定向到https
当用户提交到购物篮并且我们使用https并且没有cookie时,创建并显示新购物篮并发送回cookie
当用户提交到购物篮并且我们使用https并且有一个有效的cookie且新商品用于与第一个商品不同的商品时,在购物篮中添加一行并显示两行
当用户提交篮子并且我们使用https并且有一个有效的cookie并且新项目与前一个产品相同时,增加该篮子线的数量并显示两条线
当用户点击篮子页面上的“结帐”并使用https并且有一个cookie并且篮子非空并且......
I've read http://eggsonbread.com/2010/03/28/my-rspec-best-practices-and-tips/ which advises i.a that each "it block" should contain only one assertion: instead of doing the computation and then testing several attributes in the same block, use a "before" inside a context to create (or retrieve) the object under test and assign it to @some_instance_variable, then write each attribute test as a separate block. That helps a little, but in a case such as outlined above where testing step n requires doing all the setup for steps [1..n-1] I find myself either duplicating setup code (obviously not good) or creating lots of helper functions with increasingly unwieldy names (def create_basket_with_three_lines_and_two_products) and calling them consecutively in each step's before block.
我已经阅读了http://eggsonbread.com/2010/03/28/my-rspec-best-practices-and-tips/,它建议每个“it block”应该只包含一个断言:而不是进行计算然后在同一个块中测试多个属性,在上下文中使用“before”来创建(或检索)测试对象并将其分配给@some_instance_variable,然后将每个属性测试写为单独的块。这有点帮助,但在如上所述的情况下,测试步骤n需要执行步骤[1..n-1]的所有设置,我发现自己要么重复设置代码(显然不好),要么创建大量辅助函数越来越笨拙的名字(def create_basket_with_three_lines_and_two_products)并在阻止之前的每个步骤中连续调用它们。
Any tips on how to do this less verbosely/tediously? I appreciate the general principle behind the idea that each example should not depend on state left behind by previous examples, but when you're testing a multi-step process and things can go wrong at any step, setting up the context for each step is inevitably going to require rerunning all the setup for the previous n steps, so ...
关于如何做到这一点的任何提示都不那么冗长/繁琐?我理解这个想法背后的一般原则,即每个示例都不应该依赖前面示例留下的状态,但是当您测试多步骤过程并且在任何步骤都可能出错时,为每个步骤设置上下文是不可避免地要求重新运行前n个步骤的所有设置,所以......
2 个解决方案
#1
2
Here's one possible approach -- define an object that creates the necessary state for each step and pass it forward for each successive one. Basically you need to mock/stub the method calls for all the setup conditions:
这是一种可能的方法 - 定义一个对象,为每个步骤创建必要的状态,并为每个步骤传递它。基本上你需要模拟/存根所有设置条件的方法调用:
class MultiStep
def initialize(context)
@context = context
end
def init_vars
@cut = @context.instance_variable_get(:@cut)
end
def setup(step)
init_vars
method(step).call
end
def step1
@cut.stub(:foo).and_return("bar")
end
def step2
step1
@cut.stub(:foo_bar).and_return("baz_baz")
end
end
class Cut # Class Under Test
def foo
"foo"
end
def foo_bar
"foo_bar"
end
end
describe "multiple steps" do
before(:each) do
@multi_stepper = MultiStep.new(self)
@cut = Cut.new
end
it "should setup step1" do
@multi_stepper.setup(:step1)
@cut.foo.should == "bar"
@cut.foo_bar.should == "foo_bar"
end
it "should setup step2" do
@multi_stepper.setup(:step2)
@cut.foo.should == "bar"
@cut.foo_bar.should == "baz_baz"
end
end
#2
1
Certainly too late for OP, but this could be handy for others - the rspec-steps gem seems to be built for this exact situation: https://github.com/LRDesign/rspec-steps
对于OP来说当然为时已晚,但这对其他人来说可能很方便 - rspec-steps gem似乎是针对这种情况构建的:https://github.com/LRDesign/rspec-steps
It might be worthwhile to look at https://github.com/railsware/rspec-example_steps and https://github.com/jimweirich/rspec-given as well. I settled on rspec-steps, but I was in a rush and these other options might actually be better for all I know.
看看https://github.com/railsware/rspec-example_steps和https://github.com/jimweirich/rspec-given也许是值得的。我决定采用rspec步骤,但我很匆忙,其他选项可能实际上对我所知道的更好。
#1
2
Here's one possible approach -- define an object that creates the necessary state for each step and pass it forward for each successive one. Basically you need to mock/stub the method calls for all the setup conditions:
这是一种可能的方法 - 定义一个对象,为每个步骤创建必要的状态,并为每个步骤传递它。基本上你需要模拟/存根所有设置条件的方法调用:
class MultiStep
def initialize(context)
@context = context
end
def init_vars
@cut = @context.instance_variable_get(:@cut)
end
def setup(step)
init_vars
method(step).call
end
def step1
@cut.stub(:foo).and_return("bar")
end
def step2
step1
@cut.stub(:foo_bar).and_return("baz_baz")
end
end
class Cut # Class Under Test
def foo
"foo"
end
def foo_bar
"foo_bar"
end
end
describe "multiple steps" do
before(:each) do
@multi_stepper = MultiStep.new(self)
@cut = Cut.new
end
it "should setup step1" do
@multi_stepper.setup(:step1)
@cut.foo.should == "bar"
@cut.foo_bar.should == "foo_bar"
end
it "should setup step2" do
@multi_stepper.setup(:step2)
@cut.foo.should == "bar"
@cut.foo_bar.should == "baz_baz"
end
end
#2
1
Certainly too late for OP, but this could be handy for others - the rspec-steps gem seems to be built for this exact situation: https://github.com/LRDesign/rspec-steps
对于OP来说当然为时已晚,但这对其他人来说可能很方便 - rspec-steps gem似乎是针对这种情况构建的:https://github.com/LRDesign/rspec-steps
It might be worthwhile to look at https://github.com/railsware/rspec-example_steps and https://github.com/jimweirich/rspec-given as well. I settled on rspec-steps, but I was in a rush and these other options might actually be better for all I know.
看看https://github.com/railsware/rspec-example_steps和https://github.com/jimweirich/rspec-given也许是值得的。我决定采用rspec步骤,但我很匆忙,其他选项可能实际上对我所知道的更好。