Eu costumo usar os blocos anteriores para definir variáveis de instância. Eu então uso essas variáveis nos meus exemplos. Eu me deparei recentemente let()
. De acordo com os documentos do RSpec, é usado para
... para definir um método auxiliar memorizado. O valor será armazenado em cache em várias chamadas no mesmo exemplo, mas não em exemplos.
Como isso difere do uso de variáveis de instância nos blocos anteriores? E também quando você deve usar let()
vs before()
?
Respostas:
Eu sempre prefiro
let
uma variável de instância por alguns motivos:nil
, o que pode levar a erros sutis e falsos positivos. Comolet
cria um método, você receberá umNameError
quando digita incorretamente, o que eu acho preferível. Também facilita a refatoração de especificações.before(:each)
gancho será executado antes de cada exemplo, mesmo que o exemplo não use nenhuma das variáveis de instância definidas no gancho. Isso geralmente não é grande coisa, mas se a configuração da variável de instância demorar muito, você estará perdendo ciclos. Para o método definido porlet
, o código de inicialização é executado apenas se o exemplo o chamar.@
).let
e manter meuit
bloco agradável e curto.Um link relacionado pode ser encontrado aqui: http://www.betterspecs.org/#let
fonte
let
para definir todos os objetos dependentes ebefore(:each)
para definir a configuração necessária ou quaisquer zombarias / stubs necessários nos exemplos. Eu prefiro isso a um grande anzol que contém tudo isso. Além disso,let(:foo) { Foo.new }
é menos barulhento (e mais direto ao ponto) entãobefore(:each) { @foo = Foo.new }
. Aqui está um exemplo de como eu usá-lo: github.com/myronmarston/vcr/blob/v1.7.0/spec/vcr/util/...NoMethodError
receber um aviso, mas YMMV.foo = Foo.new(...)
e depois usuáriosfoo
nas linhas posteriores. Mais tarde, você escreve um novo exemplo no mesmo grupo de exemplos que também precisa serFoo
instanciado da mesma maneira. Neste ponto, você deseja refatorar para eliminar a duplicação. Você pode remover asfoo = Foo.new(...)
linhas dos seus exemplos e substituí-las por umalet(:foo) { Foo.new(...) }
alteração na forma como os exemplos são usadosfoo
. Mas se você refatorar,before { @foo = Foo.new(...) }
também precisará atualizar as referências nos exemplos defoo
para@foo
.A diferença entre a utilização de instâncias variáveis e
let()
é quelet()
é preguiçoso-avaliada . Isso significa quelet()
não é avaliado até que o método definido seja executado pela primeira vez.A diferença entre
before
elet
é quelet()
fornece uma boa maneira de definir um grupo de variáveis em um estilo 'em cascata'. Ao fazer isso, a especificação parece um pouco melhor, simplificando o código.fonte
let
se precisar de algo para ser avaliado sempre? por exemplo, preciso que um modelo filho esteja presente no banco de dados antes que algum comportamento seja acionado no modelo pai. Não estou necessariamente referenciando esse modelo filho no teste, porque estou testando o comportamento dos modelos pais. No momento, estou usando olet!
método, mas talvez seja mais explícito colocar essa configuraçãobefore(:each)
?Substituí completamente todos os usos de variáveis de instância em meus testes rspec para usar let (). Eu escrevi um exemplo rápido para um amigo que o usou para dar uma pequena aula sobre Rspec: http://ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html
Como algumas das outras respostas aqui dizem, let () é avaliado preguiçosamente, portanto, ele carregará apenas os que precisam ser carregados. Seca as especificações e torna-as mais legíveis. Na verdade, eu portamos o código let () Rspec para uso em meus controladores, no estilo da gema herdated_resource. http://ruby-lambda.blogspot.com/2010/06/stealing-let-from-rspec.html
Juntamente com a avaliação lenta, a outra vantagem é que, combinado com o ActiveSupport :: Concern e a especificação / suporte / comportamento de carregar tudo, você pode criar sua própria mini-DSL de especificação específica para seu aplicativo. Eu escrevi alguns para testar contra recursos Rack e RESTful.
A estratégia que eu uso é Factory-everything (via Machinist + Forgery / Faker). No entanto, é possível usá-lo em combinação com os blocos before (: each) para pré-carregar fábricas para um conjunto inteiro de grupos de exemplos, permitindo que as especificações funcionem mais rapidamente: http://makandra.com/notes/770-taking-advantage -de-rspec-s-deixar-antes-de-blocos
fonte
# spec/friendship_spec.rb
e# spec/comment_spec.rb
exemplo, você não acha que eles torná-lo menos legível? Eu não tenho idéia de ondeusers
veio e precisará cavar mais fundo.let()
os últimos dias e, pessoalmente, não vejo diferença, exceto pela primeira vantagem que Myron mencionou. E não tenho tanta certeza de deixar ir e o que não, talvez porque sou preguiçoso e gosto de ver código antecipadamente sem precisar abrir mais um arquivo. Obrigado por seus comentários.É importante ter em mente que let é avaliado preguiçosamente e não coloca métodos de efeito colateral nele, caso contrário você não seria capaz de mudar de let para before (: each) facilmente. Você pode usar let! em vez de deixar, para que seja avaliado antes de cada cenário.
fonte
Em geral,
let()
é uma sintaxe mais agradável e economiza@name
símbolos de digitação em todo o lugar. Mas, ressalva emptor! Eu encontreilet()
também introduz bugs sutis (ou pelo menos arranhões na cabeça) porque a variável realmente não existe até que você tente usá-la ... Diga sinal de conto: se adicionar umputs
depois dolet()
para ver se a variável está correta permite uma especificação para passar, mas sem aputs
especificação falhar - você encontrou essa sutileza.Também descobri que
let()
isso não parece armazenar em cache em todas as circunstâncias! Eu escrevi no meu blog: http://technicaldebt.com/?p=1242Talvez seja apenas eu?
fonte
let
sempre memoriza o valor pela duração de um único exemplo. Ele não memoriza o valor em vários exemplos.before(:all)
, por outro lado, permite reutilizar uma variável inicializada em vários exemplos.let!
é para isso que ela foi projetada. relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/...let é funcional, pois é essencialmente um Proc. Também está em cache.
Uma pegadinha que eu encontrei imediatamente com let ... Em um bloco Spec que está avaliando uma alteração.
Você precisará ligar
let
fora do seu bloco de espera. ou seja, você está ligandoFactoryGirl.create
no seu bloco let. Geralmente faço isso verificando se o objeto persiste.Caso contrário, quando o
let
bloco for chamado pela primeira vez, uma alteração no banco de dados ocorrerá devido à instanciação lenta.Atualizar
Apenas adicionando uma nota. Tenha cuidado ao jogar código golf ou, neste caso, rspec golf com esta resposta.
Nesse caso, só preciso chamar algum método ao qual o objeto responde. Então, eu invoco o
_.persisted?
método _ no objeto como sua verdade. Tudo o que estou tentando fazer é instanciar o objeto. Você poderia ligar em branco? ou nada? também. A questão não é o teste, mas trazer o objeto da vida, chamando-o.Então você não pode refatorar
ser estar
como o objeto não foi instanciado ... é preguiçoso. :)
Atualização 2
alavancar o let! sintaxe para criação instantânea de objetos, o que deve evitar esse problema completamente. Note que isso derrotará muitos dos propósitos da preguiça do let não batido.
Além disso, em alguns casos, você pode realmente aproveitar a sintaxe do assunto em vez de deixar, pois isso pode oferecer opções adicionais.
fonte
Nota para Joseph - se você estiver criando objetos de banco de dados em um,
before(:all)
eles não serão capturados em uma transação e é muito mais provável que você deixe cruft em seu banco de dados de teste. Use embefore(:each)
vez disso.A outra razão para usar let e sua avaliação preguiçosa é para que você possa pegar um objeto complicado e testar peças individuais substituindo let em contextos, como neste exemplo muito elaborado:
fonte
"antes" por padrão implica
before(:each)
. Ref O Livro Rspec, copyright 2010, página 228.Eu uso
before(:each)
para propagar alguns dados para cada grupo de exemplo sem precisar chamar olet
método para criar os dados no bloco "it". Menos código no bloco "it" neste caso.Eu uso
let
se eu quiser alguns dados em alguns exemplos, mas não em outros.Antes e let são ótimos para secar os blocos "it".
Para evitar qualquer confusão, "let" não é o mesmo que
before(:all)
. "Let" reavalia seu método e valor para cada exemplo ("it"), mas armazena em cache o valor em várias chamadas no mesmo exemplo. Você pode ler mais sobre isso aqui: https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-letfonte
Voz dissidente aqui: após 5 anos de rspec, não gosto
let
muito.1. A avaliação preguiçosa geralmente torna confusa a configuração do teste
Torna-se difícil argumentar sobre a instalação quando algumas coisas que foram declaradas na instalação não estão realmente afetando o estado, enquanto outras o estão.
Eventualmente, por frustração, alguém muda
let
paralet!
(a mesma coisa sem avaliação preguiçosa) para obter as especificações funcionando. Se isso der certo para eles, nasce um novo hábito: quando uma nova especificação é adicionada a um conjunto mais antigo e não funciona, a primeira coisa que o escritor tenta é adicionar estrondos alet
chamadas aleatórias .Em breve, todos os benefícios de desempenho terão desaparecido.
2. Sintaxe especial é incomum para usuários não rspec
Prefiro ensinar Ruby ao meu time do que os truques do rspec. Variáveis de instância ou chamadas de método são úteis em todos os lugares deste projeto e em outras, a
let
sintaxe será útil apenas no rspec.3. Os "benefícios" nos permitem ignorar facilmente boas alterações de design
let()
é bom para dependências caras que não queremos criar repetidamente. Também combina bem comsubject
, permitindo que você seque repetidamente chamadas para métodos com vários argumentosDependências caras repetidas muitas vezes e métodos com grandes assinaturas são os dois pontos em que poderíamos melhorar o código:
Em todos esses casos, posso resolver o sintoma de testes difíceis com um bálsamo calmante de magia rspec, ou posso tentar resolver a causa. Sinto que passei muito dos últimos anos no primeiro e agora quero um código melhor.
Para responder à pergunta original: eu preferiria não, mas ainda uso
let
. Eu o uso principalmente para combinar com o estilo do resto da equipe (parece que a maioria dos programadores do Rails no mundo agora está profundamente envolvida em sua mágica rspec, o que é muito frequente). Às vezes, uso-o quando estou adicionando um teste a algum código que não tenho controle ou que não tenho tempo para refatorar para uma melhor abstração: por exemplo, quando a única opção é o analgésico.fonte
Eu uso
let
para testar minhas respostas HTTP 404 em minhas especificações de API usando contextos.Para criar o recurso, eu uso
let!
. Mas para armazenar o identificador de recurso, eu usolet
. Veja como fica:Isso mantém as especificações limpas e legíveis.
fonte