开发者

testing a multi-step workflow in rspec

I'd like to know about idioms or best practices for testing a multi-step workflow using 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. 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
  3. 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
  4. 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
  5. when user clicks 'checkout' on the basket page and is using https and there is a cookie and the basket is non-empty and ...
  6. ...

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.

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 ...


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


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

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜