Nice write up, thanks for sharing.

On Monday, July 9, 2012 10:26:18 AM UTC-7, Karl wrote:
>
> Not really a problem, but something I didn't really look out for because 
> all my tests were passing... and hey, if tests are passing, I sleep well.
>
> *TL;DR Dumped let{}, removed FactoryGirl associations, back to 
> before(:all) and instance variable/FG.build pattern = super speedy tests. 
> *
> *
> *
> *TL;DR.1 And read the docs.*
>
> *Background*
> I need to test a fairly complex permission matrix at the model level. To 
> properly test it, I need to build out several complete company structures, 
> with a multitude of users at various authorization levels. And 200+ actual 
> tests.
>
> Being a fan of rspec's let{}, I decide to use it copiously. 40 lines worth.
>
> In these tests, I don't really need to have persisted records, so using 
> FactoryGirl.build_stubbed fits the bill here.
>
> let(:company) { FactoryGirl.build_stubbed(:company)}
>
> ... an so on, 39 times. 
>
>  
>
> *What I Found*
> I thought one particular spec was running a little slow. So I converted 
> the FactoryGirl.build to the shiny new FactoryGirl.build_stubbed. No 
> difference in time. So I decided to watch the test.log. Well, I quickly saw 
> what was happening... it was persisting *thousands* of records.
>
> But wait. I was using FactoryGirl.build_stubbed. Where were all these 
> records coming from?
>
>
> *Problem #1*
> In most of my FactoryGirl factories, I was using 'association :area', 
> 'association 
> :store', and so on. Didn't think much about these until yesterday.
>
> Turns out that FactoryGirl will build/create and *save* those associated 
> factories, even if you are using FactoryGirl.build or 
> FactoryGirl.build_stubbed. Learned something new there. Honestly, I 
> didn't expect this behavior, but I understand why. Shoulda read the docs.
>
> Now, the easy way around this is to pass into a 
> FactoryGirl.create/build/build_stubbed a nil for the association, if it 
> is not needed. Ala:
>
> FactoryGirl.build_stubbed(:store, company: fg_company, area: nil)
>
>
> Now it won't build out the associated area. Alas, I had forgotten *
> just one* of these nil associations. And at the worst possible 
> relationship level, the bottom. So every time one factory was built, it 
> create the entire supporting structure above. Thus, every hit to an 
> let(:invoice) builds an entire company structure from one single 
> FactoryGirl.build_stubbed(:invoice) call.
>
> But it get's worse.
>
>
> *Problem #2*
> I love the let{}. But to be honest, I never read the docs on it. Well, I 
> did read them yesterday. Relavent line being (emphasis added):
>
> The value will be cached across multiple calls *in the same example but 
> not across examples*.
>
>
> Uh-oh. Let{} is like a before(:each). Which is what most specs need. But 
> I don't, not for this spec. I'm never modifying the AR records, just 
> testing some methods within models, which don't modify AR instances.
>
>
> *Resulting Big Problem*
> Ok, not really a problem. But certainly very, very inefficient.
>
> By forgetting to nil an association in a FactoryGirld.build_stubbed, and 
> with let{} recreating an entire company structure, to the database, for 
> every 200+ test. Well, you get the picture. It's slooooow. 22 seconds worth 
> of slow.
>
>
> *Solution*
> You know I wouldn't drag you along this far without a solution.
>
>    1. Just remove all the 'association :model' statements from all 
>    FactoryGirl definitions. I know they are handy, but I want CONTROL over my 
>    factories. And just one small mistake can make a spec run many X-times 
>    longer.
>    2. Remove the let{} and replace with the good ol' instance 
>    variable/build pattern.
>    3. Move all the instance variables into a before(:all).
>
> before(:all) do
>   @company = FactoryGirl.build_stubbed(:company)
>
>   @store = FactoryGirl.build_stubbed(:store, company: company) 
>
>   ... and so on, 38 times.
> end
>
>
> Note for step #1. It caused me to refactor some other specs as well. This 
> turned out to be a good thing, as I was able to speed up several other 
> specs, and add some clarity to those specs that required building out a 
> company structure.
>
> *Results*
> 2.5 seconds. Not too shabby.
>
> After the refactoring for all tests, I dropped ~30 more seconds off the 
> entire suite.
>
>
> Hope this helps someone else out there improve the speed of their specs 
> too.
>

-- 
You received this message because you are subscribed to the Google Groups 
"rspec" group.
To view this discussion on the web visit 
https://groups.google.com/d/msg/rspec/-/MIkevtgDxVAJ.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/rspec?hl=en.

Reply via email to