Boa prática em testes & cuidados com FactoryGirl

FactoryGirl é uma das melhores opções para criação de factories no Rails. Seu uso contínuo se torna conveniênte em equipes, já que esta aplicação gera objetos carregados de dados de maneira simples, prontos para serem utilizados num ambiente de teste. No entanto, tal comodidade pode vir a ser a futura dor de cabeça no quesito performance.

Irei mostrar os principais motivos pelo qual devemos ter cuidado ao utilizar o FactoryGirl.

Existiria grande diferença entre estes dois exemplos?

1
@event = Factory.build :event
1
@event = Event.new

Tudo depende de nossa definição na factory Event:

1
2
3
4
5
6
7
8
9
10
FactoryGirl.define do
  factory :event do
    organizer
    name 'Corrida'
    sequence(:slug) { |n| "corrida#{n}" }
    address 'Avenida 123 - n 12'
    phone '(22) 2734-0503'
    email 'hi@mail.com'
  end
end

Utilizando esta factory nós não iremos apenas criar um novo objeto Event. Criaremos também um objeto Organizer, que é uma dependência de Event. Ou seja, estamos acessando a factory de organizer e definindo um objeto que muitas vezes nem iremos utilizar no teste.


Performance


1
2
Benchmark.realtime { 100.times { FactoryGirl.build :event } }
=> 12.933986
1
2
Benchmark.realtime { 100.times { Event.new } }
=> 0.021129

Este exemplo mostra a implicação do uso inconsequente da FactoryGirl. Imagine quanto tempo acabamos perdendo em testes unitários que devem ser rodados constantemente. O principal fator causador de problemas de performance no primeiro caso é o acesso ao banco de dados. O FactoryGirl#build, apesar de não salvar o próprio objeto Event, ele possui um comportamento que sempre salva as dependências do objeto passado para este método, neste caso, o objeto Organizer.

Muitas vezes, a melhor opção para os casos em que precisemos do objeto salvo no banco de dados é um simples #create.


Alto acoplamento


Velocidade nos testes é uma qualidade essencial, porém este não é o principal argumento para evitar o uso do FactoryGirl em testes unitários.

Ambientes de teste crescem, ficam complexos (assim como a aplicação), e chegam no ponto de visível lentidão para a prática de TDD por motivos citados acima. Quando isto acontece em testes unitários, algo está errado. Acabamos aceitando uma certa comodidade criada pelo alto acoplamento dos objetos gerados pelo FactoryGirl, e perdemos a especificidade do teste em questão.

Exemplo:

Imagine a utilização desta factory:

1
2
3
4
5
6
7
8
9
FactoryGirl.define do
  factory :event do
    organizer
    expired false
    open_to_enrollment true
    name 'Corrida'
    email 'hi@mail.com'
  end
end

Facilmente percebemos que caso alguém tente descobrir a utilização do método Event#can_enroll? pelo primeiro exemplo (utilizando FactoryGirl), não entenderá de fato o que o método faz.

Ou seja, além de ser muito mais custoso, estamos sendo implícitos.

Com FactoryGirl

1
2
3
4
5
6
describe Event do
  it '#can_enroll?' do
    event = FactoryGirl.build :event, open_to_enrollments: true
    expect(event.can_enroll?).to be_true
  end
end

Sem FactoryGirl

1
2
3
4
5
6
describe Event do
  it '#can_enroll?' do
    event = Event.new open_to_enrollments: true, expired: false
    expect(event.can_enroll?).to be_true
  end
end

Definição do método

1
2
3
4
5
class Event
  def can_enroll?
    open_to_enrollments && !expired
  end
end

A questão é que sempre haverão mais validações, e mais dependências, causando um custo muito maior a médio/longo prazo, onde técnicas como Mocking & Stubbing se encaixam perfeitamente, tornando os testes específicos e principalmente unitários novamente.