2009 m. gruodis 11 d., penktadienis

Namo :)

2009 m. lapkritis 28 d., šeštadienis

Blueprints VS. Factory Girl: speed comparison

Introduction

Last saturday I wrote an introduction to my new gem Blueprints. You might want to read it before reading this. Today I wanted to compare the performance of them.

Spec file

I wrote 5 almost identical spec files. You can find them here. Basically what they do is create two animals (tiger and bunny) and check animal count + check each attribute for each animal. Since factory girl doesn't support any kind of cleanup after tests (I suppose it trusts the good will of fixtures) and I wasn't doing rails project, I had to emulate transactions being created and rolled back. I've also tried simply deleting all records after each test to compare performance.

Tests

SuiteBest timeWorst time
Factory girl0.897s1.058s
Factory girl (non transactional)1.332s1.537s
Blueprints0.870s0.992s
Blueprints (with preloaded)0.750s0.877s
Blueprints (with preloaded) x 100.824s0.879s


Explanations

Best time is the best time result that `time` has reported to me, worst time is worst.
Non transactional means that test didn't use transactions (all records were simply deleted after each test).
With prebuilt means that fixture data was prebuilt and saved in database (not rebuilt before every test)
x 10 means that all tests were performed 10 times.

Conclusion

Raw performance between blueprints and factory girl don't really differ, as 90% of work has to be done by database. You probably also noticed that being transactional means 50% more speed. What's more interesting is tests with prebuilt data. 70 tests with same data ran pretty much the as long 7 tests. This means you can save huge amounts of time by prebuilding data that is often used throughout tests. I've already mentioned in my last post how in one project we started prebuilding data for two users and tests started passing twice faster. For an average project that could mean instead of waiting 10 minutes for tests to finish it would only take 5! Don't you hate sitting and watching those dots?

2009 m. lapkritis 21 d., šeštadienis

Blueprints - factories and fixtures replacement for lazy typists

Why another replacement?

What is your least favorite part of testing? For me it's always been generating data for test. Long before factories were born we all were using fixtures, however they had some real limitations that I'm sure you all know about. Someone got fed up with fixtures and factories were born. They've overcome most annoyances about fixtures but introduced some new ones, namely much slower speed of tests and repetition in your tests when creating data. Of course lazy typists didn't like this approach and so the new wave (really small one) was born - using scenarios. It was hornsby scenarios plugin that introduced it, however it wasn't actively developed and was pretty archaic.
One day my team got hands on it, they fixed some things that were most annoying and made hornsby pretty usable. However too many improvements were born in our heads so we took some code from hornsby, deleted most of unnecessary code, improved the concept itself and introduced a new plugin/gem - blueprints.

What is Blueprints?

Blueprints is a mix of fixtures, factories, rake and some new concepts. Imagine a blueprint as a scenario that defines how and what object(s) is built/modified. Let's say we need an apple, here's how it would look like in blueprints (spec/blueprint.rb):

blueprint :apple do
@apple = Fruit.create!(:species => 'apple', :color => 'green')
end
And in your test file you could do:

it "should be green apple" do
build :apple
@apple.species.should == "apple"
@apple.color.should == "green"
end
Now of course I'm assuming that you have Fruit as an ActiveRecord model in your application.

This was easy, wasn't it? Blueprints can also depend on another blueprints. So for example if you want to have apple worm that eats apple, you could write this:

blueprint :apple_worm => :apple do
@apple_worm = Worm.create!(:name => 'wormie')
@apple_worm.eat(@apple)
end
Note that we have @apple instance variable in this blueprint since we added a dependency on :apple blueprint for it. Now you can

build :apple_worm
in your test and you have both - @apple and @apple_worm.

This is pretty much how original hornsby scenarios did look like (except for several annoying bugs). Now here's what we improved:
  1. Take another look at :apple blueprint, see the repetition? If you don't - check the name of blueprint and the name of instance variable (both are named apple), we took out the repetition, so whatever block of blueprint evaluates to is assigned to instance variable with the same name as blueprint. This means we could rewrite our :apple scenario to this:

    blueprint :apple do
    Fruit.create!(:species => 'apple', :color => 'green')
    end
    And once you build it in test file you still have @apple. Note that this variable is not assigned if one with the same name exists (so it doesn't accidentally override some instance variable you set in another blueprint only because you named them the same).

  2. We even took one more step forward and added an option to shorten this even more. So the same apple scenario could be written like this:

    Fruit.blueprint :apple, :species => 'apple', :color => 'green'
    As we also needed some nicer way to implement dependencies, since

    Fruit.blueprint {:apple => :apple_tree}, :species => 'apple'
    looks ugly, we introduced depends_on method that could be used like this:

    Fruit.blueprint(:apple, :species => 'apple').depends_on(:apple_tree)
    There was one more issue with dependencies. As we couldn't use instance variables out of block scope, we introduced :@ syntax. So a scenario similar to :apple_worm would look like this:

    Worm.blueprint(:apple_worm, :name => 'another wormie', :apple_to_eat => :@apple).depends_on(:apple)
    Notice the colon before @apple.

  3. We also introduced another variation of blueprint method, one without the name of scenario, which for :apple blueprint could be used like this:

    blueprint :apple do
    Fruit.blueprint :species => 'apple'
    end
    Note that the difference between using Fruit.blueprint and Fruit.create! is only that .blueprint bypasses attr_protected and attr_accessible (which is usually desired when creating test data).

  4. We've also introduced prebuilt blueprints - these are available in all test cases (similar to fixtures). As blueprints are transactional, this means that these scenarios can only be built once for all test cases (again similar to fixtures). It could really cut time that tests run if you enable them for most common blueprints. For example on one project we've had two users that were used is many test cases, so I made them preloaded, and test time literally dropped in half! You can read more about these in blueprints github wiki

  5. Namespaces were also introduced in recent version of blueprints. You can also read more about those in github wiki.



Conclusion

Now you may not like blueprints due to the fact it's harder to see what test data you have, without checking additional file. Now if you're like me - you usually memorize most usually used data and so you don't need to check it. Anyway blueprints give you more advanced features than any fixtures framework I know of at the same time giving you a flexibility to decide how much data you need in all tests and how much only in some cases. It's also one of the most concise and it can be as fast as fixtures. One limitation though is that it probably doesn't support sqlite as sqlite doesn't support transactions. It's been production tested and we haven't found any bugs. So give it a try and and tell us what could be improved or if some bug slipped. You can find blueprints as well as instructions on how to use it at http://github.com/sinsiliux/Blueprints

2009 m. gegužė 9 d., šeštadienis

Skittles vodka

Štai vienas receptas tiems, kam pabodo tradiciniai gėrimai.

Reikės:
  • 5 x 0.5l Grynos degtinės (nespalvotos)
  • ~1kg skittles
  • Piltuvėlių (kuo daugiau tuo geriau, bet iš bėdos užteks ir vieno)
  • 5 + [piltuvėlių kiekis] tuščių plastikinių butelių (1l ar didesnės talpos)
  • Popierinių rankšluosčių ar kavos filtrų
Eiga:
  1. Suskirstome skittles pagal spalvas (turėtų gautis 5 krūvelės)
  2. Supilame juos į butelius, taip kad viename butelyje būtų viena spalva
  3. Įpilame į kiekvieną butelį po 0.5l degtinės
  4. Suplakame butelius (užsuktus žinoma)
  5. Laukiame kol skittles pilnai ištirps (turėtų užtrukti apie pusantros paros). Kartas nuo karto galime paplakti, kad skittles greičiau ištirptų.
  6. Ištirpus skittles, butelių viršuje turėtų susiformuoti baltas sluoksnis, kurį reikia nufiltruoti.
  7. Dedame piltuvėlį į nepanaudotą plastikinį butelį, išklojame piltuvėlio vidų popieriniu rankšluosčiu ar kavos filtru ir iš lėto pildami spalvotą degtinę nufiltruojame.
  8. Paliekame butelį nusistovėt. Tuo metu išsiplauname piltuvėlį ir nufiltruojame kitą butelį.
  9. Jei nusistovėjus lieka baltos masės, filtruojame dar kartą.
  10. Nufiltravus visus butelius supilame turinį į senus degtinės butelius ir įdedame parai į kamerą.
  11. Degtinės likučius išgeriame iš karto arba supilame į atskirą butelį ir padarome mix'ą.
Patarimai:
  1. Vieną kartą nufiltruoti vieną butelį užtrunka keleta valandų.
  2. Filtravimas vyks greičiau, jei buteliuose pradursite skylę orui išeiti, tačiau nepamirškite, kad ta skylė egzistuoja, nes aplaistysite stalą ar grindis.
  3. Filtravimas vyks dar greičiau jei vienam buteliui keletą kartų pakeisite filtrą.
  4. Filtro kraštai neturėtų užeiti už piltuvėlio ribų, nes prilaistysite šalia butelio.
  5. Gerkite saikingai ;). Nors saldumas labai užmuša alkoholio skonį, tai vistiek yra ~35% stiprumo gėrimas.
Beveik kilogramas tuščių skittles pakelių:


Sėkmės!

2009 m. balandis 10 d., penktadienis

2009 m. kovas 23 d., pirmadienis

2009 m. kovas 14 d., šeštadienis

Sveikinimai

Šiandien 03 14 žinoma kaip tarptautinė matematikų diena dėl datos panašumo į skaičių Pi. Sveikinimai mieliems kolegoms ir visiems prijaučiantiems matematikai! Pažadu už jus (ir šiek tiek už save, vis tik MIF'e studijuoju) šiandien išgerti bokalą alaus ;).