[rspec-users] Reusing story snippets

Jim Morris wolfmanjm at gmail.com
Tue Jun 17 20:17:22 EDT 2008


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


More information about the rspec-users mailing list