测试rspec中的多步骤工作流程

时间:2023-01-12 12:46:56

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

让我们以购物过程可能是“购物车”系统为例

  1. when user submits to basket and we are not using https, redirect to https
  2. 当用户提交到购物篮并且我们没有使用https时,重定向到https

  3. 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
  4. 当用户提交到购物篮并且我们使用https并且没有cookie时,创建并显示新购物篮并发送回cookie

  5. 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
  6. 当用户提交到购物篮并且我们使用https并且有一个有效的cookie且新商品用于与第一个商品不同的商品时,在购物篮中添加一行并显示两行

  7. 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
  8. 当用户提交篮子并且我们使用https并且有一个有效的cookie并且新项目与前一个产品相同时,增加该篮子线的数量并显示两条线

  9. when user clicks 'checkout' on the basket page and is using https and there is a cookie and the basket is non-empty and ...
  10. 当用户点击篮子页面上的“结帐”并使用https并且有一个cookie并且篮子非空并且......

  11. ...

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步骤,但我很匆忙,其他选项可能实际上对我所知道的更好。