Benchmark entre RSpec e Shoulda
13 de agosto de 2009 - 16:25
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.
class CreatePosts < ActiveRecord::Migration def self.up create_table :posts do |t| t.string :title t.text :content t.timestamps end end def self.down drop_table :posts end 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.
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."
end
class PostTest < ActiveSupport::TestCase context "Post with defaults" do setup do @post = Factory(:post) end
10_000.times do |i| should "do assertion #{i}" do assert_equal @post.title, "Lorem ipsum dolor sit amet" end end end
context "Post with custom title" do setup do @post = Factory(:post, :title => "Lorem ipsum dolor sit amet FTW") end
10_000.times do |i| should "do assertion #{i}" do assert_equal @post.title, "Lorem ipsum dolor sit amet FTW" end end end
end Shoulda sem Factory Girl
Os testes sem Factory Girl utilizaram a abordagem de se ter um método para criar o objeto.
class PostTest < ActiveSupport::TestCase context "Post with defaults" do setup do @post = create_post end
10_000.times do |i| should "do assertion #{i}" do assert_equal @post.title, "Lorem ipsum dolor sit amet" end end end
context "Post with custom title" do setup do @post = create_post(:title => "Lorem ipsum dolor sit amet FTW") end
10_000.times do |i| should "do assertion #{i}" do assert_equal @post.title, "Lorem ipsum dolor sit amet FTW" end end end
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
end RSpec com Factory Girl
Para testar o RSpec com Factory Gir, foi utilizado o código abaixo.
describe Post do describe "with defaults" do before do @post = Factory(:post) end
10_000.times do |i| it "should do assertion #{i}" do @post.title.should == "Lorem ipsum dolor sit amet" end end end
describe "with custom title" do before do @post = Factory(:post, :title => "Lorem ipsum dolor sit amet FTW") end
10_000.times do |i| it "should do assertion #{i}" do @post.title.should == "Lorem ipsum dolor sit amet FTW" end end end
end RSpec sem Factory Girl
E aqui vão os testes escritos para RSpec sem utilizar o Factory Girl.
describe Post do describe "with defaults" do before do @post = create_post end
10_000.times do |i| it "should do assertion #{i}" do @post.title.should == "Lorem ipsum dolor sit amet" end end end
describe "with custom title" do before do @post = create_post(:title => "Lorem ipsum dolor sit amet FTW") end
10_000.times do |i| it "should do assertion #{i}" do @post.title.should == "Lorem ipsum dolor sit amet FTW" end end end
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
end Test::Unit com Factory Girl
class PostTest < ActiveSupport::TestCase def setup @post = Factory(:post) end
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
end
class PostWithCustomTitleTest < ActiveSupport::TestCase def setup @post = Factory(:post, :title => "Lorem ipsum dolor sit amet FTW") end
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
en Test::Unit sem Factory Girl
class PostTest < ActiveSupport::TestCase def setup @post = create_post end
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
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
end
class PostWithCustomTitleTest < ActiveSupport::TestCase def setup @post = create_post(:title => "Lorem ipsum dolor sit amet FTW") end
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
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
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!
Pessoalmente, nunca usei o Shoulda em meus projetos pessoais. E, com certeza, não será agora que irei utilizá-lo!
Se quiser adicionar algum benchmark, veja o código utilizado nestes testes no Github: http://github.com/fnando/benchmark-rspec-shoulda/.
Update: Adicionei os tempos para os testes usando Test::Unit.










Comentários (0)