I find myself doing this: Scenario "logged in user visiting the home page" do Given "A logged in user" do a_logged_in_user end When "..." Then "..." end The a_logged_in_user method is a helper method in helper.rb which sets up the state so that the user can browse the website. Later in the story of course, I can just do ''Given "A logged in user" and it will get the previous definition. Is there any way to avoid that duplicated Given call at the top of almost every story? Mikel
On Sat, Jun 14, 2008 at 5:03 AM, Mikel Lindsaar <raasdnil at gmail.com> wrote:> I find myself doing this: > > Scenario "logged in user visiting the home page" do > Given "A logged in user" do > a_logged_in_user > end > > When "..." > Then "..." > end > > The a_logged_in_user method is a helper method in helper.rb which sets > up the state so that the user can browse the website. > > Later in the story of course, I can just do ''Given "A logged in user" > and it will get the previous definition. > > Is there any way to avoid that duplicated Given call at the top of > almost every story?If it''s implicit in almost every story, just call the helper in the first step of your scenarios. This might be an issue if you''re striving to recycle steps, but otherwise it works perfectly well. If the story obviously requires authentication, why bother specifying it? Maybe others will see it differently, but I omit a lot of the "scaffolding" preconditions when it''s clear from context: Given a user editing their account details Of course they have to be logged in for that. I may write a single scenario that shows users can''t get to their account details page without being logged in, but the rest can happily just imply it: Given("a user editing their account details") do a_logged_in_user browse_to(''account details page'') end k
On Jun 14, 2008, at 5:03 AM, Mikel Lindsaar wrote:> I find myself doing this: > > Scenario "logged in user visiting the home page" do > Given "A logged in user" do > a_logged_in_user > end > > When "..." > Then "..." > endThings have evolved a bit since Story Runner first came out. The approach you are using here is what we call in-line steps, and was the only option back then. You can now use disconnected steps in both plain text and Ruby: require ''steps/visitors'' Story "logged in users see more stuff", %( As a registered user I want to see more stuff than non-registered users So I can feel like I''m getting some benefit in return for giving up my personal information ), :steps => :visitors do Scenario "logged in user visits home page" do Given "I am logged in as David" When "I visit the home page" Then "I should see the message ''Welcome David''" end end # in steps/visitors steps_for :visitors do Given "I am logged in as $name" do |name| # create a user w/ name and log in as that user end When ".." Then ".." end This approach really cleans up the story code leaving the informative bits while hiding the redundant detail. HTH, David> The a_logged_in_user method is a helper method in helper.rb which sets > up the state so that the user can browse the website. > > Later in the story of course, I can just do ''Given "A logged in user" > and it will get the previous definition. > > Is there any way to avoid that duplicated Given call at the top of > almost every story? > > Mikel > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
Along similar lines is there a way to do the equivalent of before(:all) and after(:all) or after(:each) in stories? Basically I have a similar situation as above, but I need to make sure the user is logged out after each scenario. or that the user is logged in once at the start of all scenarios then logged out after them regardless of errors etc. Thanks On Jun 14, 7:40 am, David Chelimsky <dchelim... at gmail.com> wrote:> On Jun 14, 2008, at 5:03 AM, Mikel Lindsaar wrote: > > > I find myself doing this: > > > Scenario "logged in user visiting the home page" do > > Given "A logged in user" do > > a_logged_in_user > > end > > > When "..." > > Then "..." > > end > > Things have evolved a bit since Story Runner first came out. The > approach you are using here is what we call in-line steps, and was the > only option back then. > > You can now use disconnected steps in both plain text and Ruby: > > require ''steps/visitors'' > > Story "logged in users see more stuff", %( > As a registered user > I want to see more stuff than non-registered users > So I can feel like I''m getting some benefit in return for giving > up my personal information > ), :steps => :visitors do > Scenario "logged in user visits home page" do > Given "I am logged in as David" > When "I visit the home page" > Then "I should see the message ''Welcome David''" > end > end > > # in steps/visitors > > steps_for :visitors do > Given "I am logged in as $name" do |name| > # create a user w/ name and log in as that user > end > > When ".." > Then ".." > end > > This approach really cleans up the story code leaving the informative > bits while hiding the redundant detail. > > HTH, > David > > > The a_logged_in_user method is a helper method in helper.rb which sets > > up the state so that the user can browse the website. > > > Later in the story of course, I can just do ''Given "A logged in user" > > and it will get the previous definition. > > > Is there any way to avoid that duplicated Given call at the top of > > almost every story? > > > Mikel > > _______________________________________________ > > rspec-users mailing list > > rspec-us... at rubyforge.org > >http://rubyforge.org/mailman/listinfo/rspec-users > > _______________________________________________ > rspec-users mailing list > rspec-us... at rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users
On Jun 16, 2008, at 6:18 PM, Jim Morris wrote:> Along similar lines is there a way to do the equivalent of > before(:all) and after(:all) or after(:each) in stories? > > Basically I have a similar situation as above, but I need to make sure > the user is logged out after each scenario. or that the user is logged > in once at the start of all scenarios then logged out after them > regardless of errors etc.Each scenario is run in its own instance of RailsStory, so it''s got a new session (IntegrationSession). So you *should* get this isolation implicitly. There are callbacks you can use if you create and register a listener, but they won''t have access to the same scope that exists inside the steps. Here''s how you do it: class MyListener def method_missing sym, *args, &block # ignore all messages you don''t care about end def run_started(num_scenarios); end def story_started(title, narrative); end etc end See http://rspec.info/rdoc/classes/Spec/Runner/Formatter/Story/PlainTextFormatter.html , which implements all the callback methods (we need better docs for it, but everything is there - don''t use collected_steps though - that should really be marked nodoc). Cheers, David> Thanks > > > On Jun 14, 7:40 am, David Chelimsky <dchelim... at gmail.com> wrote: >> On Jun 14, 2008, at 5:03 AM, Mikel Lindsaar wrote: >> >>> I find myself doing this: >> >>> Scenario "logged in user visiting the home page" do >>> Given "A logged in user" do >>> a_logged_in_user >>> end >> >>> When "..." >>> Then "..." >>> end >> >> Things have evolved a bit since Story Runner first came out. The >> approach you are using here is what we call in-line steps, and was >> the >> only option back then. >> >> You can now use disconnected steps in both plain text and Ruby: >> >> require ''steps/visitors'' >> >> Story "logged in users see more stuff", %( >> As a registered user >> I want to see more stuff than non-registered users >> So I can feel like I''m getting some benefit in return for giving >> up my personal information >> ), :steps => :visitors do >> Scenario "logged in user visits home page" do >> Given "I am logged in as David" >> When "I visit the home page" >> Then "I should see the message ''Welcome David''" >> end >> end >> >> # in steps/visitors >> >> steps_for :visitors do >> Given "I am logged in as $name" do |name| >> # create a user w/ name and log in as that user >> end >> >> When ".." >> Then ".." >> end >> >> This approach really cleans up the story code leaving the informative >> bits while hiding the redundant detail. >> >> HTH, >> David >> >>> The a_logged_in_user method is a helper method in helper.rb which >>> sets >>> up the state so that the user can browse the website. >> >>> Later in the story of course, I can just do ''Given "A logged in >>> user" >>> and it will get the previous definition. >> >>> Is there any way to avoid that duplicated Given call at the top of >>> almost every story? >> >>> Mikel >>> _______________________________________________ >>> rspec-users mailing list >>> rspec-us... at rubyforge.org >>> http://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
I''m not using Rails, I am doing end to end integration testing talking to the server via net/http, so RailsStory is not involved. I think the listeners may do it, I can use story_started like before(:all) and story_ended like after(:all) which will be great, presuming story_ended is always called even after a failure. However I am missing the place where that listener gets registered, I am using the... Story "description", %{...}, :steps_for => steps do Scenario "sdsdsd" do ... end end syntax, so where is the listener set? Thanks On Jun 16, 6:32 pm, David Chelimsky <dchelim... at gmail.com> wrote:> On Jun 16, 2008, at 6:18 PM, Jim Morris wrote: > > > Along similar lines is there a way to do the equivalent of > > before(:all) and after(:all) or after(:each) in stories? > > > Basically I have a similar situation as above, but I need to make sure > > the user is logged out after each scenario. or that the user is logged > > in once at the start of all scenarios then logged out after them > > regardless of errors etc. > > Each scenario is run in its own instance of RailsStory, so it''s got a > new session (IntegrationSession). So you *should* get this isolation > implicitly. > > There are callbacks you can use if you create and register a listener, > but they won''t have access to the same scope that exists inside the > steps. Here''s how you do it: > > class MyListener > def method_missing sym, *args, &block > # ignore all messages you don''t care about > end > > def run_started(num_scenarios); end > def story_started(title, narrative); end > > etc > end > > Seehttp://rspec.info/rdoc/classes/Spec/Runner/Formatter/Story/PlainTextF... > , which implements all the callback methods (we need better docs for > it, but everything is there - don''t use collected_steps though - that > should really be marked nodoc). > > Cheers, > David > > > > > Thanks > > > On Jun 14, 7:40 am, David Chelimsky <dchelim... at gmail.com> wrote: > >> On Jun 14, 2008, at 5:03 AM, Mikel Lindsaar wrote: > > >>> I find myself doing this: > > >>> Scenario "logged in user visiting the home page" do > >>> Given "A logged in user" do > >>> a_logged_in_user > >>> end > > >>> When "..." > >>> Then "..." > >>> end > > >> Things have evolved a bit since Story Runner first came out. The > >> approach you are using here is what we call in-line steps, and was > >> the > >> only option back then. > > >> You can now use disconnected steps in both plain text and Ruby: > > >> require ''steps/visitors'' > > >> Story "logged in users see more stuff", %( > >> As a registered user > >> I want to see more stuff than non-registered users > >> So I can feel like I''m getting some benefit in return for giving > >> up my personal information > >> ), :steps => :visitors do > >> Scenario "logged in user visits home page" do > >> Given "I am logged in as David" > >> When "I visit the home page" > >> Then "I should see the message ''Welcome David''" > >> end > >> end > > >> # in steps/visitors > > >> steps_for :visitors do > >> Given "I am logged in as $name" do |name| > >> # create a user w/ name and log in as that user > >> end > > >> When ".." > >> Then ".." > >> end > > >> This approach really cleans up the story code leaving the informative > >> bits while hiding the redundant detail. > > >> HTH, > >> David > > >>> The a_logged_in_user method is a helper method in helper.rb which > >>> sets > >>> up the state so that the user can browse the website. > > >>> Later in the story of course, I can just do ''Given "A logged in > >>> user" > >>> and it will get the previous definition. > > >>> Is there any way to avoid that duplicated Given call at the top of > >>> almost every story? > > >>> Mikel > >>> _______________________________________________ > >>> rspec-users mailing list > >>> rspec-us... at rubyforge.org > >>>http://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-us... at rubyforge.org > >http://rubyforge.org/mailman/listinfo/rspec-users > > _______________________________________________ > rspec-users mailing list > rspec-us... at rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users
Ahh ok think I found it... In my test file at the start... 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) Then I define my steps using StepGroup.new Then the Scenarios It seems to work although not very intuitive :) I''d prefer a before(:all) and after(:all) On Jun 16, 11:58 pm, Jim Morris <wolfma... at gmail.com> wrote:> I''m not using Rails, I am doing end to end integration testing talking > to the server via net/http, so RailsStory is not involved. > > I think the listeners may do it, I can use story_started like > before(:all) and story_ended like after(:all) which will be great, > presuming story_ended is always called even after a failure. > > However I am missing the place where that listener gets registered, I > am using the... > > Story "description", %{...}, :steps_for => steps do > Scenario "sdsdsd" do > ... > end > end > > syntax, so where is the listener set? > > Thanks > > On Jun 16, 6:32 pm, David Chelimsky <dchelim... at gmail.com> wrote: > > > On Jun 16, 2008, at 6:18 PM, Jim Morris wrote: > > > > Along similar lines is there a way to do the equivalent of > > > before(:all) and after(:all) or after(:each) in stories? > > > > Basically I have a similar situation as above, but I need to make sure > > > the user is logged out after each scenario. or that the user is logged > > > in once at the start of all scenarios then logged out after them > > > regardless of errors etc. > > > Each scenario is run in its own instance of RailsStory, so it''s got a > > new session (IntegrationSession). So you *should* get this isolation > > implicitly. > > > There are callbacks you can use if you create and register a listener, > > but they won''t have access to the same scope that exists inside the > > steps. Here''s how you do it: > > > class MyListener > > def method_missing sym, *args, &block > > # ignore all messages you don''t care about > > end > > > def run_started(num_scenarios); end > > def story_started(title, narrative); end > > > etc > > end > > > Seehttp://rspec.info/rdoc/classes/Spec/Runner/Formatter/Story/PlainTextF... > > , which implements all the callback methods (we need better docs for > > it, but everything is there - don''t use collected_steps though - that > > should really be marked nodoc). > > > Cheers, > > David > > > > Thanks > > > > On Jun 14, 7:40 am, David Chelimsky <dchelim... at gmail.com> wrote: > > >> On Jun 14, 2008, at 5:03 AM, Mikel Lindsaar wrote: > > > >>> I find myself doing this: > > > >>> Scenario "logged in user visiting the home page" do > > >>> Given "A logged in user" do > > >>> a_logged_in_user > > >>> end > > > >>> When "..." > > >>> Then "..." > > >>> end > > > >> Things have evolved a bit since Story Runner first came out. The > > >> approach you are using here is what we call in-line steps, and was > > >> the > > >> only option back then. > > > >> You can now use disconnected steps in both plain text and Ruby: > > > >> require ''steps/visitors'' > > > >> Story "logged in users see more stuff", %( > > >> As a registered user > > >> I want to see more stuff than non-registered users > > >> So I can feel like I''m getting some benefit in return for giving > > >> up my personal information > > >> ), :steps => :visitors do > > >> Scenario "logged in user visits home page" do > > >> Given "I am logged in as David" > > >> When "I visit the home page" > > >> Then "I should see the message ''Welcome David''" > > >> end > > >> end > > > >> # in steps/visitors > > > >> steps_for :visitors do > > >> Given "I am logged in as $name" do |name| > > >> # create a user w/ name and log in as that user > > >> end > > > >> When ".." > > >> Then ".." > > >> end > > > >> This approach really cleans up the story code leaving the informative > > >> bits while hiding the redundant detail. > > > >> HTH, > > >> David > > > >>> The a_logged_in_user method is a helper method in helper.rb which > > >>> sets > > >>> up the state so that the user can browse the website. > > > >>> Later in the story of course, I can just do ''Given "A logged in > > >>> user" > > >>> and it will get the previous definition. > > > >>> Is there any way to avoid that duplicated Given call at the top of > > >>> almost every story? > > > >>> Mikel > > >>> _______________________________________________ > > >>> rspec-users mailing list > > >>> rspec-us... at rubyforge.org > > >>>http://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-us... at rubyforge.org > > >http://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-us... at rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users
Reordered your posts so my comments make sense (we prefer to avoid top-posting - even though I sometimes violate that myself :) ).> On Jun 16, 11:58 pm, Jim Morris <wolfma... at gmail.com> wrote: >> I''m not using Rails, I am doing end to end integration testing talking >> to the server via net/http, so RailsStory is not involved.Cool. Are you talking directly through ruby constructs or through a browser tool like selenium?>> I think the listeners may do it, I can use story_started like >> before(:all) and story_ended like after(:all) which will be great, >> presuming story_ended is always called even after a failure.Yep.>> However I am missing the place where that listener gets registered, I >> am using the... >> >> Story "description", %{...}, :steps_for => steps do >> Scenario "sdsdsd" do >> ... >> end >> end >> >> syntax, so where is the listener set? >> >> ThanksOn Tue, Jun 17, 2008 at 2:28 AM, Jim Morris <wolfmanjm at gmail.com> wrote:> Ahh ok think I found it... > > In my test file at the start... > > 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)Yep. That''s the right way.> Then I define my steps using StepGroup.newYou can just use the steps_for method and it''ll still work.> Then the Scenarios > > It seems to work although not very intuitive :) > > I''d prefer a before(:all) and after(:all)I''m not with you on that, but I''m open :) We could solve the OP''s problem with something like this: Before Each Scenario: Log in as admin Scenario: editing private data ... Before Each Scenario would match a Given step definition "Log in as $role" or something like that. I''m just not sure this really heads us down the right path. Next thing you know we''ll have Before Story, After Each Scenario, After Story, etc and the stories are going to start looking less and less like stories and more and more and more like code, at which point you should be using example groups instead :) Anybody else have opinions about that? Cheers, David>> >> On Jun 16, 6:32 pm, David Chelimsky <dchelim... at gmail.com> wrote: >> >> > On Jun 16, 2008, at 6:18 PM, Jim Morris wrote: >> >> > > Along similar lines is there a way to do the equivalent of >> > > before(:all) and after(:all) or after(:each) in stories? >> >> > > Basically I have a similar situation as above, but I need to make sure >> > > the user is logged out after each scenario. or that the user is logged >> > > in once at the start of all scenarios then logged out after them >> > > regardless of errors etc. >> >> > Each scenario is run in its own instance of RailsStory, so it''s got a >> > new session (IntegrationSession). So you *should* get this isolation >> > implicitly. >> >> > There are callbacks you can use if you create and register a listener, >> > but they won''t have access to the same scope that exists inside the >> > steps. Here''s how you do it: >> >> > class MyListener >> > def method_missing sym, *args, &block >> > # ignore all messages you don''t care about >> > end >> >> > def run_started(num_scenarios); end >> > def story_started(title, narrative); end >> >> > etc >> > end >> >> > Seehttp://rspec.info/rdoc/classes/Spec/Runner/Formatter/Story/PlainTextF... >> > , which implements all the callback methods (we need better docs for >> > it, but everything is there - don''t use collected_steps though - that >> > should really be marked nodoc). >> >> > Cheers, >> > David >> >> > > Thanks >> >> > > On Jun 14, 7:40 am, David Chelimsky <dchelim... at gmail.com> wrote: >> > >> On Jun 14, 2008, at 5:03 AM, Mikel Lindsaar wrote: >> >> > >>> I find myself doing this: >> >> > >>> Scenario "logged in user visiting the home page" do >> > >>> Given "A logged in user" do >> > >>> a_logged_in_user >> > >>> end >> >> > >>> When "..." >> > >>> Then "..." >> > >>> end >> >> > >> Things have evolved a bit since Story Runner first came out. The >> > >> approach you are using here is what we call in-line steps, and was >> > >> the >> > >> only option back then. >> >> > >> You can now use disconnected steps in both plain text and Ruby: >> >> > >> require ''steps/visitors'' >> >> > >> Story "logged in users see more stuff", %( >> > >> As a registered user >> > >> I want to see more stuff than non-registered users >> > >> So I can feel like I''m getting some benefit in return for giving >> > >> up my personal information >> > >> ), :steps => :visitors do >> > >> Scenario "logged in user visits home page" do >> > >> Given "I am logged in as David" >> > >> When "I visit the home page" >> > >> Then "I should see the message ''Welcome David''" >> > >> end >> > >> end >> >> > >> # in steps/visitors >> >> > >> steps_for :visitors do >> > >> Given "I am logged in as $name" do |name| >> > >> # create a user w/ name and log in as that user >> > >> end >> >> > >> When ".." >> > >> Then ".." >> > >> end >> >> > >> This approach really cleans up the story code leaving the informative >> > >> bits while hiding the redundant detail. >> >> > >> HTH, >> > >> David >> >> > >>> The a_logged_in_user method is a helper method in helper.rb which >> > >>> sets >> > >>> up the state so that the user can browse the website. >> >> > >>> Later in the story of course, I can just do ''Given "A logged in >> > >>> user" >> > >>> and it will get the previous definition. >> >> > >>> Is there any way to avoid that duplicated Given call at the top of >> > >>> almost every story? >> >> > >>> Mikel >> > >>> _______________________________________________ >> > >>> rspec-users mailing list >> > >>> rspec-us... at rubyforge.org >> > >>>http://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-us... at rubyforge.org >> > >http://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-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 >
David Chelimsky wrote:> Reordered your posts so my comments make sense (we prefer to avoid > top-posting - even though I sometimes violate that myself :) ). > > >> On Jun 16, 11:58 pm, Jim Morris <wolfma... at gmail.com> wrote: >> >>> I''m not using Rails, I am doing end to end integration testing talking >>> to the server via net/http, so RailsStory is not involved. >>> > > Cool. Are you talking directly through ruby constructs or through a > browser tool like selenium? > > >>> I think the listeners may do it, I can use story_started like >>> before(:all) and story_ended like after(:all) which will be great, >>> presuming story_ended is always called even after a failure. >>> > > Yep. > > >>> However I am missing the place where that listener gets registered, I >>> am using the... >>> >>> Story "description", %{...}, :steps_for => steps do >>> Scenario "sdsdsd" do >>> ... >>> end >>> end >>> >>> syntax, so where is the listener set? >>> >>> Thanks >>> > > On Tue, Jun 17, 2008 at 2:28 AM, Jim Morris <wolfmanjm at gmail.com> wrote: > >> Ahh ok think I found it... >> >> In my test file at the start... >> >> 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) >> > > Yep. That''s the right way. > > >> Then I define my steps using StepGroup.new >> > > You can just use the steps_for method and it''ll still work. > > >> Then the Scenarios >> >> It seems to work although not very intuitive :) >> >> I''d prefer a before(:all) and after(:all) >> > > I''m not with you on that, but I''m open :) We could solve the OP''s > problem with something like this: > > Before Each Scenario: > Log in as admin > > Scenario: editing private data > ... > > Before Each Scenario would match a Given step definition "Log in as > $role" or something like that. > > I''m just not sure this really heads us down the right path. Next thing > you know we''ll have Before Story, After Each Scenario, After Story, > etc and the stories are going to start looking less and less like > stories and more and more and more like code, at which point you > should be using example groups instead :) > > Anybody else have opinions about that? > > Cheers, > David > >I *really* don''t like the idea of having a "Before Each Scenario" or similar construct. I have already seen abuses of the story runner where the stories look too much like code/examples. I think adding such language to the story runner would really hurt and confuse the original intent of stories. I''m not questioning whether such an addition would be helpful, I think it would and I may at times use it.. but IMO it would only be helpful to the developers point of view and I don''t see it really adding much value to the stories. If someone can offer an example where they think such language would help the stakeholder I would be interested but to me it is starting too look too much like examples. Like Kyle said in a previous post, I think a better way to handle these situations is to rely on the fact that having an admin be logged in is somewhat implicit in the story and that the implementation can be handled under the covers, so to speak, with helper methods within the steps. This of course will require you to call the same helper method for all of your first Given steps. So I guess this is where the pain point is.. but it doesn''t seem to be a big one most of the time. The Listeners are good for application-wide/story suite wide setup and cleanup, like databases as such. Placing the login of an admin into a listener would obviously be overkill unless all of your stories involved an Admin being logged in. What people seem to asking for is more control over the granularity of which stories a particular listener effects. Say, for example you have a set of stories that all require that an Admin account to exist with certain permissions and that the admin is logged in. What if you could define your AdminSetupListener and then hook it into the runner for your stories like so: with_steps_for(:admin_section, :webrat).and_listeners_for(:admin_setup) do run_story(File.expand_path(__FILE__)) end This could provide the setup/teardown capabilities for a certain set of stories without polluting the Story language. Does that make sense what I''m suggesting? I''m not sold entirely on it myself. It seems to add yet another layer of abstraction to help DRY up the steps and in so doing spreads the story''s executable code throughout even more files. This could hurt maintainability so I think the use of this could also be abused... but if people want more granular control in there setup and teardown with the Stories I would vote for an option like this instead of placing it in the actual Story language/parser itself. I hope most of that makes sense. :) -Ben
On Jun 17, 2008, at 10:41 AM, Ben Mabey wrote:> David Chelimsky wrote: >> Reordered your posts so my comments make sense (we prefer to avoid >> top-posting - even though I sometimes violate that myself :) ). >> >> >>> On Jun 16, 11:58 pm, Jim Morris <wolfma... at gmail.com> wrote: >>> >>>> I''m not using Rails, I am doing end to end integration testing >>>> talking >>>> to the server via net/http, so RailsStory is not involved. >>>> >> >> Cool. Are you talking directly through ruby constructs or through a >> browser tool like selenium? >> >> >>>> I think the listeners may do it, I can use story_started like >>>> before(:all) and story_ended like after(:all) which will be great, >>>> presuming story_ended is always called even after a failure. >>>> >> >> Yep. >> >> >>>> However I am missing the place where that listener gets >>>> registered, I >>>> am using the... >>>> >>>> Story "description", %{...}, :steps_for => steps do >>>> Scenario "sdsdsd" do >>>> ... >>>> end >>>> end >>>> >>>> syntax, so where is the listener set? >>>> >>>> Thanks >>>> >> >> On Tue, Jun 17, 2008 at 2:28 AM, Jim Morris <wolfmanjm at gmail.com> >> wrote: >> >>> Ahh ok think I found it... >>> >>> In my test file at the start... >>> >>> 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) >>> >> >> Yep. That''s the right way. >> >> >>> Then I define my steps using StepGroup.new >>> >> >> You can just use the steps_for method and it''ll still work. >> >> >>> Then the Scenarios >>> >>> It seems to work although not very intuitive :) >>> >>> I''d prefer a before(:all) and after(:all) >>> >> >> I''m not with you on that, but I''m open :) We could solve the OP''s >> problem with something like this: >> >> Before Each Scenario: >> Log in as admin >> >> Scenario: editing private data >> ... >> >> Before Each Scenario would match a Given step definition "Log in as >> $role" or something like that. >> >> I''m just not sure this really heads us down the right path. Next >> thing >> you know we''ll have Before Story, After Each Scenario, After Story, >> etc and the stories are going to start looking less and less like >> stories and more and more and more like code, at which point you >> should be using example groups instead :) >> >> Anybody else have opinions about that? >> >> Cheers, >> David >> >> > I *really* don''t like the idea of having a "Before Each Scenario" or > similar construct. I have already seen abuses of the story runner > where the stories look too much like code/examples. I think adding > such language to the story runner would really hurt and confuse the > original intent of stories. I''m not questioning whether such an > addition would be helpful, I think it would and I may at times use > it.. but IMO it would only be helpful to the developers point of > view and I don''t see it really adding much value to the stories. If > someone can offer an example where they think such language would > help the stakeholder I would be interested but to me it is starting > too look too much like examples. > > Like Kyle said in a previous post, I think a better way to handle > these situations is to rely on the fact that having an admin be > logged in is somewhat implicit in the story and that the > implementation can be handled under the covers, so to speak, with > helper methods within the steps. This of course will require you to > call the same helper method for all of your first Given steps. So I > guess this is where the pain point is.. but it doesn''t seem to be a > big one most of the time. > > The Listeners are good for application-wide/story suite wide setup > and cleanup, like databases as such. Placing the login of an admin > into a listener would obviously be overkill unless all of your > stories involved an Admin being logged in. What people seem to > asking for is more control over the granularity of which stories a > particular listener effects. Say, for example you have a set of > stories that all require that an Admin account to exist with certain > permissions and that the admin is logged in. What if you could > define your AdminSetupListener and then hook it into the runner for > your stories like so: > > with_steps_for > (:admin_section, :webrat).and_listeners_for(:admin_setup) do > run_story(File.expand_path(__FILE__)) > end > > This could provide the setup/teardown capabilities for a certain set > of stories without polluting the Story language. > > Does that make sense what I''m suggesting? I''m not sold entirely on > it myself. It seems to add yet another layer of abstraction to help > DRY up the steps and in so doing spreads the story''s executable code > throughout even more files. This could hurt maintainability so I > think the use of this could also be abused... but if people want > more granular control in there setup and teardown with the Stories I > would vote for an option like this instead of placing it in the > actual Story language/parser itself. > > I hope most of that makes sense. :)Perfect sense to me :) Cheers, David> > > -Ben > > > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
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
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
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
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
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>
Hi Dan, Thanks for the analysis. Yes I am a strong proponent of using the best tools for the job, even it means bending the rules. I like the view of the stakeholder being another program, it certainly will make the phrasing of the header easier as I was struggling with that.> 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?In this case of a pure integration test where the server is usually remote there is no easy way to fake a login. I am exercising the actual WEB service API as if I were a real client. I have plenty of Unit tests using Mocks for testing the server at the method level of course.> ...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 tableI also do that especially for setting up data in a database before running the test, the trick is to be able to clean the database from whatever state it was in previously.So I have things like "Given the database has no entries" which will truncate the tables etc. But these kinds of tests usually have to run locally as the database is generally not accessible remotely through the various firewalls etc. Setting up Sessions though means you actually need to go through the various states to get the session setup, rather than plugging something into a session, which would be more of a functional test and not an integration test. I have to say that using the steps and scenarios model for integration testing is very DRY and much easier to write than doing it with Example groups. When I get some time I''ll write a blog entry on how I use rspec, stories and various helpers to do this integration testing. Thanks for the feedback. On Jun 18, 10:59 am, "Dan North" <tasta... at gmail.com> wrote:> 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 <wolfma... 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-us... at rubyforge.org > >http://rubyforge.org/mailman/listinfo/rspec-users > > > > _______________________________________________ > rspec-users mailing list > rspec-us... at rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users