Benchmark entre RSpec e Shoulda
Leia em 5 minutos
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.