Benchmark entre RSpec e Shoulda

Em um projeto que estou trabalhando atualmente, a suíte de testes (que utiliza Shoulda e Factory Girl) demora aproximadamente 26 minutos para ser executada. Esse tempo de execução é extremamente inaceitável, já que uma das premissas do Test-Driven Development é que sua suíte de testes seja executada o mais rápido possível!

Sem nenhum embasamento, sempre achei que o tempo excessivo se dava ao uso do Factory Girl, devido sua interação com o banco de dados. Hoje, decidi tirar a prova e fiquei surpreso com alguns números obtidos em um benchmark entre RSpec e Shoulda, como você pode conferir abaixo.

Preparando o ambiente

O primeiro passo, foi criar um aplicativo novo com apenas um único modelo chamado Post.

1.class CreatePosts < ActiveRecord::Migration def self.up create_table :posts do |t| t.string :title t.text :content
2.t.timestamps end end
3.def self.down drop_table :posts end
4.end

Antes de realizar o benchmark, foram executados os comandos rake db:migrate e rake db:test:prepare. Depois, foram criados branches específicos para cada um dos testes.

Foram realizados 4 tipos de teste:

  • Shoulda com Factory Girl
  • Shoulda sem Factory Girl
  • RSpec com Factory Girl
  • RSpec sem Factory Girl

Todos os testes acima foram executados no Ruby 1.8.7 (2009-06-08 patchlevel 173) e Ruby 1.9.1 ruby 1.9.1 (2009-07-16 p243 revision 24175) com Rails 2.3.3. Os tempos foram obtidos utilizando o comando time ao executar a rake task padrão com o comando time rake.

Veja abaixo como foram os testes realizados.

Shoulda com Factory Girl

A primeira medição foi feita utilizando Shoulda com Factory Girl. Para criar o objeto, foi utilizada a factory abaixo.

1.Factory.define :post do |f| f.title "Lorem ipsum dolor sit amet" f.content "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
2.end
3.
4.class PostTest < ActiveSupport::TestCase context "Post with defaults" do setup do @post = Factory(:post) end
5.10_000.times do |i| should "do assertion #{i}" do assert_equal @post.title, "Lorem ipsum dolor sit amet" end end end
6.context "Post with custom title" do setup do @post = Factory(:post, :title => "Lorem ipsum dolor sit amet FTW") end
7.10_000.times do |i| should "do assertion #{i}" do assert_equal @post.title, "Lorem ipsum dolor sit amet FTW" end end end
8.end

Shoulda sem Factory Girl

Os testes sem Factory Girl utilizaram a abordagem de se ter um método para criar o objeto.

1.class PostTest < ActiveSupport::TestCase context "Post with defaults" do setup do @post = create_post end
2.10_000.times do |i| should "do assertion #{i}" do assert_equal @post.title, "Lorem ipsum dolor sit amet" end end end
3.context "Post with custom title" do setup do @post = create_post(:title => "Lorem ipsum dolor sit amet FTW") end
4.10_000.times do |i| should "do assertion #{i}" do assert_equal @post.title, "Lorem ipsum dolor sit amet FTW" end end end
5.private def create_post(options={}) Post.create({ :title => "Lorem ipsum dolor sit amet", :content => "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." }.merge(options)) end
6.end

RSpec com Factory Girl

Para testar o RSpec com Factory Gir, foi utilizado o código abaixo.

1.describe Post do describe "with defaults" do before do @post = Factory(:post) end
2.10_000.times do |i| it "should do assertion #{i}" do @post.title.should == "Lorem ipsum dolor sit amet" end end end
3.describe "with custom title" do before do @post = Factory(:post, :title => "Lorem ipsum dolor sit amet FTW") end
4.10_000.times do |i| it "should do assertion #{i}" do @post.title.should == "Lorem ipsum dolor sit amet FTW" end end end
5.end

RSpec sem Factory Girl

E aqui vão os testes escritos para RSpec sem utilizar o Factory Girl.

1.describe Post do describe "with defaults" do before do @post = create_post end
2.10_000.times do |i| it "should do assertion #{i}" do @post.title.should == "Lorem ipsum dolor sit amet" end end end
3.describe "with custom title" do before do @post = create_post(:title => "Lorem ipsum dolor sit amet FTW") end
4.10_000.times do |i| it "should do assertion #{i}" do @post.title.should == "Lorem ipsum dolor sit amet FTW" end end end
5.private def create_post(options={}) Post.create({ :title => "Lorem ipsum dolor sit amet", :content => "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." }.merge(options)) end
6.end

Test::Unit com Factory Girl

1.class PostTest < ActiveSupport::TestCase def setup @post = Factory(:post) end
2.10_000.times do |i| self.class_eval <<-TXT def test_assertion_#{i} assert_equal @post.title, "Lorem ipsum dolor sit amet" end TXT end
3.end
4.
5.class PostWithCustomTitleTest < ActiveSupport::TestCase def setup @post = Factory(:post, :title => "Lorem ipsum dolor sit amet FTW") end
6.10_000.times do |i| self.class_eval <<-TXT def test_assertion_#{i} assert_equal @post.title, "Lorem ipsum dolor sit amet FTW" end TXT end
7.en

Test::Unit sem Factory Girl

01.class PostTest < ActiveSupport::TestCase def setup @post = create_post end
02.10_000.times do |i| self.class_eval <<-TXT def test_assertion_#{i} assert_equal @post.title, "Lorem ipsum dolor sit amet" end TXT end
03.private def create_post(options={}) Post.create({ :title => "Lorem ipsum dolor sit amet", :content => "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." }.merge(options)) end
04.end
05.
06.class PostWithCustomTitleTest < ActiveSupport::TestCase def setup @post = create_post(:title => "Lorem ipsum dolor sit amet FTW") end
07.10_000.times do |i| self.class_eval <<-TXT def test_assertion_#{i} assert_equal @post.title, "Lorem ipsum dolor sit amet FTW" end TXT end
08.private def create_post(options={}) Post.create({ :title => "Lorem ipsum dolor sit amet", :content => "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." }.merge(options)) end
09.end

Resultados

Antes de mostrar os resultados, quero dizer que fiquei extremamente impressionado com os resultados obtidos com Shoulda no Ruby 1.9.1; houve uma diminuição de pelo menos 5 minutos em relação ao mesmo teste executado no Ruby 1.8.7. No caso do RSpec, não houve diferença significativa.

O Test::Unit saiu em desvantagem nesse benchmark; para adicionar os testes, foi utilizado o método class_eval, que é uma operação bastante dispendiosa.

E se você não aguenta mais esperar pelos números, aqui vão eles:

Ruby 1.8.7 Ruby 1.9.1
Rspec com Factory Girl 1m35.028s 1m10.277s
Rspec sem Factory Girl 1m36.353s 1m11.002s
Shoulda com Factory Girl 8m20.456s 3m33.408s
Shoulda sem Factory Girl 8m35.973s 3m35.687s
Test::Unit com Factory Girl 1m38.559s 1m39.538s
Test::Unit sem Factory Girl 1m38.944s 1m40.026s

Como eu jamais podia esperar, o Shoulda é o problema e não o Factory Girl! Embora eu acredite que a implementação do Shoulda seja infinitamente mais simples que a do RSpec, ela consegue ser muito mais lenta!

Fonte: http://wmonline.com.br/desenvolvimento/ruby/benchmark-entre-rspec-e-shoulda

Deixe um comentário