[rspec-users] Reusing story snippets

Dan North tastapod at gmail.com
Wed Jun 18 13:59:25 EDT 2008


The bottom line here is in understanding who is the stakeholder for this
story - i.e. who cares about it, who will read it and who wants to know when
it goes wrong.

It seems to me you are using the construct of scenarios to describe machine
interactions: the request and response of service invocations. In this case
the stakeholder is actually another (client) application so it's appropriate
to change the rules a bit.

As someone else said (or it might have been you!), the point of any agile
process is to adapt to the context and help you get work done, not to adhere
to a rigid set of rules. It looks to me like you've found a solution to your
specific situation, which is a trade-off between the Rule of Idempotency
(all scenarios should be idempotent, i.e. rerunnable) and the Rule of
Independence (scenarios should be runnable in any sequence) on the one hand,
and the annoyance of having to log in for each scenario on the other.

As David said, having story-level setup is a smell, but if you go in there
with your eyes (nose?) open and you understand the trade-offs then you
should be ok.

fwiw I would probably define a separate Listener for each type of story
setup/teardown that is named for why it's there, rather than a generic
before/after story thing that I attach blocks to. So in this instance I
would have a LoginOnceBeforeEachStoryListener.

One other question: is there a way you can programmatically set the state of
"logged in" without having to actually log in via the app? For the Givens I
think it's perfectly acceptable to just "make it so". Whether that's putting
some value in a session or shoving some data in a table. It's only the When
steps that should be duty bound to operate the app like a regular user,
because that's the behaviour you are exercising. If so you could have a
super-fast "Given $user is logged in" step that runs at the start of each
scenario.

Cheers,
Dan


2008/6/18 Jim Morris <wolfmanjm at gmail.com>:

> Ok here is my sugar, put in spec_helper.rb
>
> require 'rubygems'
> require 'spec'
> require 'spec/story'
>
> # simulate before(:all) and after(:all) for stories
> class MyListener
>  def set_before(&block)
>    @before= block
>  end
>
>  def set_after(&block)
>    @after= block
>  end
>
>  def method_missing sym, *args, &block
>    # ignore all messages you don't care about
>  end
>
>  def story_started(title, narrative)
>     @before.call if @before
>   end
>
>  def story_ended(title, narrative)
>     @after.call if @after
>  end
> end
>
> def setup_listener(listener)
>  unless $my_listener
>    Spec::Story::Runner.register_listener(listener)
>    $my_listener= listener
>  end
> end
>
> def before_story(&block)
>  listener ||= MyListener.new
>  listener.set_before(&block)
>  setup_listener(listener)
> end
>
> def after_story(&block)
>  listener ||= MyListener.new
>  listener.set_after(&block)
>  setup_listener(listener)
> end
>
> Use it in your story ruby file thusly...
>
> require File.join(File.dirname(__FILE__), "spec_helper")
>
> # execute this code before story runs
> before_story do
>  #stuff
> end
>
> # execute this code after the story runs
> after_story do
>  # more stuff
> end
>
>
> I'm sure it could be cleaner!
>
> On Jun 17, 4:44 pm, Jim Morris <wolfma... at gmail.com> wrote:
> > Yep thats exactly the case, an entire Story specific setup/teardown
> > for the Given, When Language API
> >
> > Scenario setup is obviously done in the Given, although a way to clean
> > up if the spec fails would be nice.
> >
> > The listener works fine for this, its just not a very intuitive way of
> > setting things up. However I could easily add some sugar
> >  to my spec_helper that does the listener setup for me, basically just
> > wrap this...
> >
> > class MyListener
> >   def method_missing sym, *args, &block
> >     # ignore all messages you don't care about
> >   end
> >
> >   def story_started(title, narrative)
> >     puts "...Started story #{title}"
> >   end
> >
> >   def story_ended(title, narrative)
> >     puts "...Ended story #{title}"
> >   end
> > end
> >
> > Spec::Story::Runner.register_listener(MyListener.new)
> >
> > On Jun 17, 3:38 pm, Ben Mabey <b... at benmabey.com> wrote:
> >
> > > Jim Morris wrote:
> > > > Hi, Not top posting (although I prefer it ;)
> >
> > > >> Cool. Are you talking directly through ruby constructs or through a
> > > >> browser tool like selenium?
> >
> > > > I have a helper that makes posts and gets and deletes and puts
> > > > directly to the server which is implements a mostly REST-ful API and
> > > > written in Java.
> > > > I use an Hpricot based matcher that implements have_xpath to test the
> > > > XML that is returned for each API call.
> >
> > > > It is a true integration test that can test the server running
> locally
> > > > or the staging server running at the Colo.
> >
> > > > I was using (and do use) Example groups for most of my integration
> > > > tests, however I started using Programmatic Stories (not plain text
> > > > stories) for a number of reasons.
> >
> > > > Firstly I prefer it looking like code so I put the whole thing in a
> > > > single file...
> >
> > > > steps = Spec::Story::StepGroup.new do |define|
> > > >   define.given("user has empty bag") do
> > > > etc
> >
> > > > Story "test user can create, get and delete bags",...., :steps_for =>
> > > > steps do
> > > >   Scenario " create bag"...
> > > >   Scenario "get bag..."
> > > >   Scenario "delete bag..."
> > > > etc
> >
> > > > I prefer this because I am a programmer, it keeps things in one place
> > > > and makes it easy to maintain. I don't have stakeholders so plain
> text
> > > > stories are just another layer for me. Although I would use them if I
> > > > did have laymen stake holders. I am testing a REST-ful AJAX like API
> > > > to a webservice, that is used by programmers, so my stakeholders
> would
> > > > be/are programmers and again prefer programmatic code rather than
> > > > plain text stories.
> >
> > > > The reason I like Stories in this case, rather than Examples, is that
> > > > for integration testing, I can test all the edge cases for the API in
> > > > the most DRY-full way. The steps are coded once and I can just define
> > > > very thin Scenarios that test different parameters being passed in
> and
> > > > exercise all those edge cases where parameters are bad or left out
> > > > etc. Doing this the "old" way using Example groups was not at all
> DRY,
> > > > but worked pretty well. (Although Example groups with tons of helpers
> > > > maybe considered equivalent).
> >
> > > > The thing I have learned about all these BDD, TDD,  XP,  AGILE things
> > > > is to be flexible and use the best tools and practices for the job.
> > > > Being single minded and saying programmers can't use stories they are
> > > > only for stake holders means we lose a good tool! (Not that I am
> > > > accusing anyone of being single minded ;)
> >
> > > > Back to why I want/need these global setup/teardowns... And BDD
> > > > purists stop reading now ;)
> >
> > > > When doing integration testing of a remote server this way you know
> > > > all the tests need to have a login and a valid Sessionid in the
> > > > cookie. You don't want to login/logoff every Scenario because it is
> > > > SLOOOW. Logging in and out does not test the API other than the login
> > > > and logoff API and that of course has been tested once already. It is
> > > > not DRY to have to call login and logout in every Given, plus you
> need
> > > > it to logout even if the tests fail. And lastly (Shudder) some tests
> > > > have to rely on the state left by a previous test, hence the session
> > > > id needs to be the same, for a group of scenarios like add a
> resource,
> > > > modify the resource then delete the resource are best done as
> separate
> > > > Scenarios, relying on the previous Scenario. It would be slow and not
> > > > DRY to have to test add, then test add, modify, then test add,
> delete,
> > > > it is better to have Scenario add, Scenario modify, Scenario delete.
> I
> > > > know this flys in the face of SOP, but it is DRY and it is efficient
> > > > (ie faster) and it works!
> >
> > > > I hope that explains where I am coming from and how I (Mis-)use these
> > > > excellent tools you have written.
> >
> > > > Thanks
> > > > Jim
> >
> > > Ahh, I forgot that the original post was not dealing with plain-text
> > > stories.  From what I understand now, you prefer the Given, When, Then
> > > language for your certain situation and want more flexibility to help
> > > DRY these programmer-only stories. That makes a lot more sense now that
> > > I'm reminded of the context, thanks.  :)
> >
> > > When testing all the different edge cases based on different parameters
> > > passed in the URL I find that nested describes (example groups), like
> > > you mentioned, provide a good way to DRY up specs.  The spec-docs also
> > > lend themselves well to readable docs for programmers wanting to use
> the
> > > API.  When using the story framework to do this are you suggesting that
> > > a story be able to have setup/teardown and also have specific
> > > setup/teardown for individual scenarios?  Or are you just suggesting
> > > setup/teardown for an entire story (and not the scenarios)?
> >
> > > That sounds reasonable and useful, so I hope I didn't come off sounding
> > > like I thought your idea was a poor one.  In the context of plaintext
> > > stories I was thinking it would be a dangerous way to go down unless
> the
> > > language helped the stakeholder.  Having more flexible setup/teardown
> > > for stories in general seems like a good idea that would help everyone
> > > though.
> >
> > > -Ben
> >
> > > _______________________________________________
> > > rspec-users mailing list
> > > rspec-us... at rubyforge.orghttp://
> rubyforge.org/mailman/listinfo/rspec-users
> >
> > _______________________________________________
> > rspec-users mailing list
> > rspec-us... at rubyforge.orghttp://
> rubyforge.org/mailman/listinfo/rspec-users
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20080618/78f23a13/attachment-0001.html>


More information about the rspec-users mailing list