RSpec: Qual é a diferença entre let e a before block?


90

Qual é a diferença entre lete um beforebloco em RSpec?

E quando usar cada um?

Qual será a boa abordagem (let ou before) no exemplo abaixo?

let(:user) { User.make !}
let(:account) {user.account.make!}

before(:each) do
 @user = User.make!
 @account = @user.account.make!
end

Eu estudei este post stackoverflow

Mas é bom definir coisas como deixar para associação como acima?


1
Basicamente, 'let' é usado por pessoas que não gostam de variáveis ​​de instância. Como uma observação lateral, você deve considerar o uso de FactoryGirl ou uma ferramenta semelhante.
apneadiving

Respostas:


146

As pessoas parecem ter explicado algumas das maneiras básicas em que diferem, mas as omitiram before(:all)e não explicam exatamente por que deveriam ser usadas.

É minha convicção que variáveis ​​de instância não têm lugar sendo usadas na grande maioria das especificações, em parte devido aos motivos mencionados nesta resposta , então não vou mencioná-los como uma opção aqui.

deixe os blocos

O código dentro de um letbloco só é executado quando referenciado, o carregamento lento significa que a ordem desses blocos é irrelevante. Isso lhe dá uma grande quantidade de energia para reduzir a configuração repetida por meio de suas especificações.

Um exemplo (extremamente pequeno e artificial) disso é:

let(:person)     { build(:person) }
subject(:result) { Library.calculate_awesome(person, has_moustache) }

context 'with a moustache' do
  let(:has_moustache) { true } 
  its(:awesome?)      { should be_true }
end

context 'without a moustache' do
  let(:has_moustache) { false } 
  its(:awesome?)      { should be_false }
end

Você pode ver que has_moustacheé definido de forma diferente em cada caso, mas não há necessidade de repetir a subjectdefinição. Algo importante notar é que letserá utilizado o último bloco definido no contexto atual. Isso é bom para definir um padrão a ser usado para a maioria das especificações, que podem ser substituídas se necessário.

Por exemplo, verificar o valor de retorno de calculate_awesomese passado um personmodelo com top_hatdefinido como verdadeiro, mas sem bigode seria:

context 'without a moustache but with a top hat' do
  let(:has_moustache) { false } 
  let(:person)        { build(:person, top_hat: true) }
  its(:awesome?)      { should be_true }
end

Outra coisa a se notar sobre os blocos let, eles não devem ser usados ​​se você estiver procurando por algo que foi salvo no banco de dados (ou seja Library.find_awesome_people(search_criteria)), pois eles não serão salvos no banco de dados a menos que já tenham sido referenciados. let!ou beforeblocos são o que deve ser usado aqui.

Além disso, nunca mais nunca usar beforea execução do gatilho de letblocos, isso é o que let!é feito para!

deixei! blocos

let!os blocos são executados na ordem em que são definidos (como um bloco anterior). A única diferença principal em relação aos blocos anteriores é que você obtém uma referência explícita a essa variável, em vez de precisar recorrer a variáveis ​​de instância.

Tal como acontece com os letblocos, se vários let!blocos forem definidos com o mesmo nome, o mais recente é o que será usado na execução. A principal diferença é que os let!blocos serão executados várias vezes se usados ​​dessa forma, enquanto o letbloco será executado apenas na última vez.

antes (: cada) dos blocos

before(:each)é o padrão antes do bloco e, portanto, pode ser referenciado como em before {}vez de especificar o completo a before(:each) {}cada vez.

É minha preferência pessoal usar beforeblocos em algumas situações básicas. Vou usar blocos antes se:

  • Estou usando mocking, stubbing ou doubles
  • Existe qualquer configuração de tamanho razoável (geralmente, isso é um sinal de que suas características de fábrica não foram configuradas corretamente)
  • Há uma série de variáveis ​​que não preciso fazer referência diretamente, mas são necessárias para configuração
  • Estou escrevendo testes de controlador funcional em trilhos e quero executar uma solicitação específica de teste (ou seja before { get :index }). Mesmo que você possa usar subjectpara isso em muitos casos, às vezes parece mais explícito se você não precisar de uma referência.

Se você estiver escrevendo grandes beforeblocos para suas especificações, verifique suas fábricas e certifique-se de compreender totalmente as características e sua flexibilidade.

blocos antes (: todos)

Eles são executados apenas uma vez, antes das especificações no contexto atual (e seus filhos). Eles podem ser usados ​​com grande vantagem se escritos corretamente, pois há certas situações que podem reduzir a execução e o esforço.

Um exemplo (que dificilmente afetaria o tempo de execução) é simular uma variável ENV para um teste, o que você só deve precisar fazer uma vez.

Espero que ajude :)


Para usuários do minitest, que sou, mas não notei a tag RSpec deste Q&A, a before(:all)opção não existe no Minitest. Aqui estão algumas soluções alternativas nos comentários: github.com/seattlerb/minitest/issues/61
MrYoshiji

O artigo referenciado é um link morto: \
Dylan Pierce

Obrigado @DylanPierce. Não consigo encontrar nenhuma cópia desse artigo, então referenciei uma resposta SO que aborda isso em vez :)
Jay

Obrigado :) muito apreciado
Dylan Pierce

1
itsnão está mais no rspec-core. Uma forma mais moderna e idiomática é it { is_expected.to be_awesome }.
Zubin

26

Quase sempre, eu prefiro let. A postagem que você vincula especifica que lettambém é mais rápido. No entanto, às vezes, quando muitos comandos precisam ser executados, eu poderia usar before(:each)porque sua sintaxe é mais clara quando muitos comandos estão envolvidos.

Em seu exemplo, eu definitivamente preferiria usar em letvez de before(:each). De modo geral, quando apenas algumas inicializações de variáveis ​​são feitas, eu costumo gostar de usar let.


15

Uma grande diferença que não foi mencionada é que as variáveis ​​definidas com letnão são instanciadas até que você as chame pela primeira vez. Portanto, embora um before(:each)bloco instancie todas as variáveis, letvamos definir várias variáveis ​​que você pode usar em vários testes, ele não as instancia automaticamente. Sem saber disso, seus testes podem voltar a se incomodar se você espera que todos os dados sejam carregados com antecedência. Em alguns casos, você pode até querer definir uma série de letvariáveis ​​e, em seguida, usar um before(:each)bloco para chamar cada letinstância apenas para ter certeza de que os dados estão disponíveis para começar.


20
Você pode usar let!para definir métodos que são chamados antes de cada exemplo. Veja os documentos RSpec .
Jim Stewart

3

Parece que você está usando o Machinist. Cuidado, você pode ver alguns problemas com o make! dentro de let (a versão non-bang) acontecendo fora da transação global de fixture (se você estiver usando fixtures transacionais também), corrompendo os dados para seus outros testes.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.