On Thu, Jul 9, 2009 at 5:40 AM, Daniel Tenner<daniel.rspec at tenner.org> wrote:> Hi all, > Like everyone (?), I use nested contexts to keep my specs well organised and > tidy. > However, I have a problem. I have various sets of specs that needs to > perform very time-expensive operations to set up the fixtures that will be > examined in the tests. Two specific examples: testing access control logic > (requires creating a whole tree of items to verify the correct access level > against each item), and project archival (which creates a project, fills it > with test data, archives/zips the project contents, then unzips them for > examination). > I tried using before(:all) to set up those costly fixtures, however I hit > upon a feature of rspec that made that less than successful: > When using before(:all) along with nested contexts, rspec actually re-runs > the before(:all) before each sub-context. So if, like me, you have your > specs neatly organised in sub-contexts, the before(:all) is actually re-run > many times. > Interestingly, when a before(:all) is run in the root context, rspec does > not actually remove the data from the database when re-running the > before(:all). "Great," I then thought, "I can just detect whether the data > is created and decide whether or not to create the objects on that basis". > Not so fast, though: Rspec may not clobber the database, but it does clobber > instance variables. In the case of the access control test, there''s about 40 > different instance variables, so keeping track of them all manually in some > global variable outside of rspec would be messy to say the least... > So my question is, is there any workaround for successfully using > before(:all) and nested specs, so that code like the following works and > doesn''t run the expensive operation more than once: > describe "Some functionality" do > ??before(:all) do > ?? ?@variable = some_expensive_operation > ??end > > ??it "should do something" do > ?? ?@variable.should do_something > ??end > > ??describe "in a specific context" do > ?? ?it "should do another thing" do > ?? ? ?@variable.should do_another_thing > ?? ?end > ??end > end > Worth noting that I''m quite happy to give up the ability to have before > blocks in the sub-contexts in order to ensure that the expensive operation > is only run once... > Your thoughts most welcome... (including, perhaps, telling me that I''m Doing > It Wrong)I wouldn''t say you''re doing it wrong or right. I think we really want both options, in the end - a before(:something) concept that is not inherited and one that is (before(:all)). There are a couple of tickets about this in lighthouse: https://rspec.lighthouseapp.com/projects/5645/tickets/632 https://rspec.lighthouseapp.com/projects/5645/tickets/819 There may be others. Please feel free to continue this in the convo on https://rspec.lighthouseapp.com/projects/5645/tickets/632.> Daniel Tenner > http://www.woobius.com > http://danieltenner.com > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
Hey Dan, 1 approach you could do is define a method within the outer describe that is called within the inner describe and within each test not contained by an inner describe. describe "Some functionality" do it "should do something" do @variable = some_expensive_operation @variable.should do_something end describe "in a specific context" do before(:all) do @variable = some_expensive_operation end it "should do another thing" do @variable.should do_another_thing end end def some_expensive_operation p "in here" end end (also uploaded to http://gist.github.com/143693) My solution is to unDRY the subcontext''s preconditions. I have pretty strong opinions about DRYing up specs at the cost of grokability. On Jul 9, 2009, at 6:40 AM, Daniel Tenner wrote:> Hi all, > > Like everyone (?), I use nested contexts to keep my specs well > organised and tidy. > > However, I have a problem. I have various sets of specs that needs > to perform very time-expensive operations to set up the fixtures > that will be examined in the tests. Two specific examples: testing > access control logic (requires creating a whole tree of items to > verify the correct access level against each item), and project > archival (which creates a project, fills it with test data, archives/ > zips the project contents, then unzips them for examination). > > I tried using before(:all) to set up those costly fixtures, however > I hit upon a feature of rspec that made that less than successful: > > When using before(:all) along with nested contexts, rspec actually > re-runs the before(:all) before each sub-context. So if, like me, > you have your specs neatly organised in sub-contexts, the > before(:all) is actually re-run many times. > > Interestingly, when a before(:all) is run in the root context, rspec > does not actually remove the data from the database when re-running > the before(:all). "Great," I then thought, "I can just detect > whether the data is created and decide whether or not to create the > objects on that basis". Not so fast, though: Rspec may not clobber > the database, but it does clobber instance variables. In the case of > the access control test, there''s about 40 different instance > variables, so keeping track of them all manually in some global > variable outside of rspec would be messy to say the least... > > So my question is, is there any workaround for successfully using > before(:all) and nested specs, so that code like the following works > and doesn''t run the expensive operation more than once: > > describe "Some functionality" do > before(:all) do > @variable = some_expensive_operation > end > > it "should do something" do > @variable.should do_something > end > > describe "in a specific context" do > it "should do another thing" do > @variable.should do_another_thing > end > end > end > > Worth noting that I''m quite happy to give up the ability to have > before blocks in the sub-contexts in order to ensure that the > expensive operation is only run once... > > Your thoughts most welcome... (including, perhaps, telling me that > I''m Doing It Wrong) > > Daniel Tenner > http://www.woobius.com > http://danieltenner.com > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users-- Zach Moazeni http://simplechatter.com
Rereading your original email, I''m thinking I may not have entirely understood your situation. 1) Looks like you''ve already defined the "some_expensive_operation" (but that''s minor) 2) Is it your goal to call "some_expensive_operation" once and only once? On Jul 9, 2009, at 10:18 AM, Zach Moazeni wrote:> Hey Dan, > > 1 approach you could do is define a method within the outer describe > that is called within the inner describe and within each test not > contained by an inner describe. > > describe "Some functionality" do > it "should do something" do > @variable = some_expensive_operation > @variable.should do_something > end > > describe "in a specific context" do > before(:all) do > @variable = some_expensive_operation > end > > it "should do another thing" do > @variable.should do_another_thing > end > end > > def some_expensive_operation > p "in here" > end > end > > (also uploaded to http://gist.github.com/143693) > > My solution is to unDRY the subcontext''s preconditions. I have > pretty strong opinions about DRYing up specs at the cost of > grokability. > > On Jul 9, 2009, at 6:40 AM, Daniel Tenner wrote: > >> Hi all, >> >> Like everyone (?), I use nested contexts to keep my specs well >> organised and tidy. >> >> However, I have a problem. I have various sets of specs that needs >> to perform very time-expensive operations to set up the fixtures >> that will be examined in the tests. Two specific examples: testing >> access control logic (requires creating a whole tree of items to >> verify the correct access level against each item), and project >> archival (which creates a project, fills it with test data, >> archives/zips the project contents, then unzips them for >> examination). >> >> I tried using before(:all) to set up those costly fixtures, however >> I hit upon a feature of rspec that made that less than successful: >> >> When using before(:all) along with nested contexts, rspec actually >> re-runs the before(:all) before each sub-context. So if, like me, >> you have your specs neatly organised in sub-contexts, the >> before(:all) is actually re-run many times. >> >> Interestingly, when a before(:all) is run in the root context, >> rspec does not actually remove the data from the database when re- >> running the before(:all). "Great," I then thought, "I can just >> detect whether the data is created and decide whether or not to >> create the objects on that basis". Not so fast, though: Rspec may >> not clobber the database, but it does clobber instance variables. >> In the case of the access control test, there''s about 40 different >> instance variables, so keeping track of them all manually in some >> global variable outside of rspec would be messy to say the least... >> >> So my question is, is there any workaround for successfully using >> before(:all) and nested specs, so that code like the following >> works and doesn''t run the expensive operation more than once: >> >> describe "Some functionality" do >> before(:all) do >> @variable = some_expensive_operation >> end >> >> it "should do something" do >> @variable.should do_something >> end >> >> describe "in a specific context" do >> it "should do another thing" do >> @variable.should do_another_thing >> end >> end >> end >> >> Worth noting that I''m quite happy to give up the ability to have >> before blocks in the sub-contexts in order to ensure that the >> expensive operation is only run once... >> >> Your thoughts most welcome... (including, perhaps, telling me that >> I''m Doing It Wrong) >> >> Daniel Tenner >> http://www.woobius.com >> http://danieltenner.com >> _______________________________________________ >> rspec-users mailing list >> rspec-users at rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users > > -- > Zach Moazeni > http://simplechatter.com > > >-- Zach Moazeni http://simplechatter.com
On Thu, Jul 9, 2009 at 9:37 AM, Daniel Tenner<daniel.rspec at tenner.org> wrote:>> 2) Is it your goal to call "some_expensive_operation" once and only once? > > Yes, exactly. In the case of the project archival spec, creating a project, > archiving it, and then unzipping it to a temporary location to look at what > was archived is a process that takes about 3 seconds on my machine. Since > archival isn''t something people will be doing live on the site, speed is not > an issue from a user point of view. However, there''s about 25 specs (in > about a dozen sub-contexts) examining the zip output... multiply that by 3 > seconds each, and that takes more time to run than all the other specs > combined! > I have actually managed to get this done using the method described in my > blog post @ > http://www.swombat.com/getting-rspec-beforeall-and-nested-contexts-w , but I > have to admit it''s a bit hackish. > David -?":apply_to_nested?=>?false" seems like a great way to do it, but I > understand why you''d want to get the refactoring out of the way first. With > my temporary hack in place, I can wait.My message about refactoring was back in January :) If you''re interested in supplying a patch I''d be interested in accepting it.> Thanks, > Daniel > On Thu, Jul 9, 2009 at 3:22 PM, Zach Moazeni <zach.lists at gmail.com> wrote: >> >> Rereading your original email, I''m thinking I may not have entirely >> understood your situation. >> >> 1) Looks like you''ve already defined the "some_expensive_operation" (but >> that''s minor) >> >> 2) Is it your goal to call "some_expensive_operation" once and only once? >> >> On Jul 9, 2009, at 10:18 AM, Zach Moazeni wrote: >> >>> Hey Dan, >>> >>> 1 approach you could do is define a method within the outer describe that >>> is called within the inner describe and within each test not contained by an >>> inner describe. >>> >>> describe "Some functionality" do >>> ?it "should do something" do >>> ? @variable = some_expensive_operation >>> ? @variable.should do_something >>> ?end >>> >>> ?describe "in a specific context" do >>> ? before(:all) do >>> ? ? @variable = some_expensive_operation >>> ? end >>> >>> ? it "should do another thing" do >>> ? ? @variable.should do_another_thing >>> ? end >>> ?end >>> >>> ?def some_expensive_operation >>> ? p "in here" >>> ?end >>> end >>> >>> (also uploaded to http://gist.github.com/143693) >>> >>> My solution is to unDRY the subcontext''s preconditions. I have pretty >>> strong opinions about DRYing up specs at the cost of grokability. >>> >>> On Jul 9, 2009, at 6:40 AM, Daniel Tenner wrote: >>> >>>> Hi all, >>>> >>>> Like everyone (?), I use nested contexts to keep my specs well organised >>>> and tidy. >>>> >>>> However, I have a problem. I have various sets of specs that needs to >>>> perform very time-expensive operations to set up the fixtures that will be >>>> examined in the tests. Two specific examples: testing access control logic >>>> (requires creating a whole tree of items to verify the correct access level >>>> against each item), and project archival (which creates a project, fills it >>>> with test data, archives/zips the project contents, then unzips them for >>>> examination). >>>> >>>> I tried using before(:all) to set up those costly fixtures, however I >>>> hit upon a feature of rspec that made that less than successful: >>>> >>>> When using before(:all) along with nested contexts, rspec actually >>>> re-runs the before(:all) before each sub-context. So if, like me, you have >>>> your specs neatly organised in sub-contexts, the before(:all) is actually >>>> re-run many times. >>>> >>>> Interestingly, when a before(:all) is run in the root context, rspec >>>> does not actually remove the data from the database when re-running the >>>> before(:all). "Great," I then thought, "I can just detect whether the data >>>> is created and decide whether or not to create the objects on that basis". >>>> Not so fast, though: Rspec may not clobber the database, but it does clobber >>>> instance variables. In the case of the access control test, there''s about 40 >>>> different instance variables, so keeping track of them all manually in some >>>> global variable outside of rspec would be messy to say the least... >>>> >>>> So my question is, is there any workaround for successfully using >>>> before(:all) and nested specs, so that code like the following works and >>>> doesn''t run the expensive operation more than once: >>>> >>>> describe "Some functionality" do >>>> ?before(:all) do >>>> ? @variable = some_expensive_operation >>>> ?end >>>> >>>> ?it "should do something" do >>>> ? @variable.should do_something >>>> ?end >>>> >>>> ?describe "in a specific context" do >>>> ? it "should do another thing" do >>>> ? ? @variable.should do_another_thing >>>> ? end >>>> ?end >>>> end >>>> >>>> Worth noting that I''m quite happy to give up the ability to have before >>>> blocks in the sub-contexts in order to ensure that the expensive operation >>>> is only run once... >>>> >>>> Your thoughts most welcome... (including, perhaps, telling me that I''m >>>> Doing It Wrong) >>>> >>>> Daniel Tenner >>>> http://www.woobius.com >>>> http://danieltenner.com >>>> _______________________________________________ >>>> rspec-users mailing list >>>> rspec-users at rubyforge.org >>>> http://rubyforge.org/mailman/listinfo/rspec-users >>> >>> -- >>> Zach Moazeni >>> http://simplechatter.com >>> >>> >>> >> >> -- >> Zach Moazeni >> http://simplechatter.com >> >> >> >> _______________________________________________ >> rspec-users mailing list >> rspec-users at rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >