J'ai tendance à utiliser des blocs précédents pour définir des variables d'instance. J'utilise ensuite ces variables dans mes exemples. Je suis récemment tombé sur let()
. Selon les documents RSpec, il est utilisé pour
... pour définir une méthode d'assistance mémo. La valeur sera mise en cache sur plusieurs appels dans le même exemple, mais pas entre les exemples.
En quoi est-ce différent d'utiliser des variables d'instance dans des blocs avant? Et aussi quand faut-il utiliser let()
vs before()
?
Je préfère toujours let
à une variable d'instance pour plusieurs raisons:
nil
, ce qui peut entraîner des bogues subtils et des faux positifs. Puisque let
crée une méthode, vous obtiendrez un NameError
lorsque vous l’orthographiez mal, ce que je trouve préférable. Cela facilite également la refactorisation des spécifications.before(:each)
sera exécuté avant chaque exemple, même si celui-ci n'utilise aucune des variables d'instance définies dans le hook. Ce n'est généralement pas grave, mais si la configuration de la variable d'instance prend beaucoup de temps, vous perdez des cycles. Pour la méthode définie par let
, le code d'initialisation ne s'exécute que si l'exemple l'appelle.@
).let
et du maintien de mon bloc it
agréable et bref.Un lien connexe peut être trouvé ici: http://www.betterspecs.org/#let
La différence entre l'utilisation de variables d'instance et let()
est que let()
est évalué paresseux. Cela signifie que let()
n'est pas évalué tant que la méthode définie n'est pas exécutée pour la première fois.
La différence entre before
et let
est que let()
vous permet de définir un groupe de variables dans un style 'en cascade'. En faisant cela, la spécification semble un peu mieux en simplifiant le code.
J'ai complètement remplacé toutes les utilisations de variables d'instance dans mes tests rspec pour utiliser let (). J'ai écrit un exemple rapide pour un ami qui l'utilisait pour enseigner une petite classe Rspec: http://Ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html
Comme le disent certaines des autres réponses ici, let () est évalué paresseux; il ne chargera donc que celles qui nécessitent un chargement. Il assèche les spécifications et les rend plus lisibles. En fait, j'ai porté le code Rspec let () à utiliser dans mes contrôleurs, à la manière de hérited_resource gem. http://Ruby-lambda.blogspot.com/2010/06/stealing-let-from-rspec.html
Outre l'évaluation paresseuse, l'autre avantage est que, combiné à ActiveSupport :: Concern et au comportement/support/tout-en-charge, vous pouvez créer votre propre mini-DSL de spécification spécifique à votre application. J'en ai écrit pour les tests sur les ressources Rack et RESTful.
La stratégie que j'utilise est Factory-all (via Machinist + Forgery/Faker). Cependant, il est possible de l’utiliser en combinaison avec des blocs before (: each) pour précharger des usines pour un ensemble complet de groupes d’exemples, permettant ainsi aux spécifications de fonctionner plus rapidement: http://makandra.com/notes/770 -prendre-avantage-de-rspec-s-laisser-dans-avant-blocs
Il est important de garder à l'esprit que let est évalué paresseux et que vous n'y mettez pas de méthode d'effet secondaire, sinon vous ne pourriez pas passer de let à avant (: chaque) facilement. Vous pouvez utiliser let! au lieu de let afin qu'il soit évalué avant chaque scénario.
En général, let()
est une syntaxe plus agréable et vous évite de taper @name
symboles partout. Mais, caveat emptor! j'ai trouvé let()
introduit également des bugs subtils (ou au moins une éraflure de la tête) car la variable n'existe pas vraiment jusqu’à ce que vous essayiez de l’utiliser ... Indiquez le signe de récit: si vous ajoutez une puts
après la let()
pour voir que la variable est correcte, une spécification est transmise, mais sans la puts
la spec échoue - vous avez trouvé cette subtilité.
J'ai aussi constaté que let()
ne semble pas se cacher dans toutes les circonstances! Je l'ai écrit dans mon blog: http://technicaldebt.com/?p=1242
Peut-être que c'est juste moi?
let est fonctionnel comme son essentiellement un Proc. Aussi sa mise en cache.
Un des pièges que j'ai tout de suite trouvé avec let ... Dans un bloc Spec qui évalue un changement.
let(:object) {FactoryGirl.create :object}
expect {
post :destroy, id: review.id
}.to change(Object, :count).by(-1)
Vous devez être sûr d'appeler let
en dehors du bloc attendu. c'est-à-dire que vous appelez FactoryGirl.create
dans votre bloc let. Je le fais habituellement en vérifiant que l'objet est persisté.
object.persisted?.should eq true
Sinon, lorsque le bloc let
est appelé pour la première fois, une modification de la base de données aura effectivement lieu en raison d'une instanciation paresseuse.
Mettre à jour
Je viens d'ajouter une note. Soyez prudent en jouant code golf ou dans ce cas, rspec golf avec cette réponse.
Dans ce cas, je dois juste appeler une méthode à laquelle l'objet répond. J'invoque donc la méthode _.persisted?
_ sur l'objet en tant que vérité. Tout ce que j'essaie de faire est d'instancier l'objet. Vous pourriez appeler vide? ou nul? aussi. Le but n'est pas le test mais apporter l'objet de la vie en l'appelant.
Donc vous ne pouvez pas refactoriser
object.persisted?.should eq true
être
object.should be_persisted
comme l'objet n'a pas été instancié ... c'est paresseux. :)
Mise à jour 2
utilisez la syntaxe let! syntaxe pour la création d'objet instantanée, ce qui devrait éviter ce problème. Notez bien que cela ira à l'encontre de l'objectif de la paresse des non-cognés.
De plus, dans certains cas, vous voudrez peut-être utiliser syntaxe du sujet au lieu de laisser, car cela peut vous donner des options supplémentaires.
subject(:object) {FactoryGirl.create :object}
Note à Joseph - Si vous créez des objets de base de données dans une before(:all)
, ils ne seront pas capturés dans une transaction et vous aurez bien plus de chances de vous laisser cruellement dans votre base de données de test. Utilisez before(:each)
à la place.
L'autre raison d'utiliser let et son évaluation paresseuse est de pouvoir prendre un objet compliqué et tester des morceaux individuels en remplaçant les contextes dans let, comme dans cet exemple très artificiel:
context "foo" do
let(:params) do
{ :foo => foo, :bar => "bar" }
end
let(:foo) { "foo" }
it "is set to foo" do
params[:foo].should eq("foo")
end
context "when foo is bar" do
let(:foo) { "bar" }
# NOTE we didn't have to redefine params entirely!
it "is set to bar" do
params[:foo].should eq("bar")
end
end
end
"avant" implique par défaut before(:each)
. Référez-vous au livre de Rspec, copyright 2010, page 228.
before(scope = :each, options={}, &block)
J'utilise before(:each)
pour créer des données pour chaque groupe d'exemples sans avoir à appeler la méthode let
pour créer les données dans le bloc "it". Moins de code dans le bloc "it" dans ce cas.
J'utilise let
si je veux des données dans certains exemples mais pas d'autres.
Les deux avant et laisser sont parfaits pour sécher les blocs "it".
Pour éviter toute confusion, "laisser" n'est pas la même chose que before(:all)
. "Let" réévalue sa méthode et sa valeur pour chaque exemple ("it"), mais met en cache la valeur sur plusieurs appels dans le même exemple. Vous pouvez en lire plus à ce sujet ici: https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let
J'utilise let
pour tester mes réponses HTTP 404 dans mes spécifications d'API à l'aide de contextes.
Pour créer la ressource, j'utilise let!
. Mais pour stocker l'identifiant de la ressource, j'utilise let
. Jetez un oeil à quoi ça ressemble:
let!(:country) { create(:country) }
let(:country_id) { country.id }
before { get "api/countries/#{country_id}" }
it 'responds with HTTP 200' { should respond_with(200) }
context 'when the country does not exist' do
let(:country_id) { -1 }
it 'responds with HTTP 404' { should respond_with(404) }
end
Cela maintient les spécifications propres et lisibles.