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