J'ai tendance à utiliser des blocs avant 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'aide mémorisée. La valeur sera mise en cache sur plusieurs appels dans le même exemple, mais pas sur plusieurs exemples.
En quoi est-ce différent de l'utilisation de variables d'instance dans des blocs avant? Et aussi quand faut-il utiliser let()
vs before()
?
Réponses:
Je préfère toujours
let
une variable d'instance pour deux raisons:nil
, ce qui peut entraîner des bogues subtils et des faux positifs. Depuislet
crée une méthode, vous obtiendrez unNameError
lorsque vous l'orthographiez mal, ce que je trouve préférable. Cela facilite également la refonte des spécifications.before(:each)
hook sera exécuté avant chaque exemple, même si l'exemple n'utilise aucune des variables d'instance définies dans le hook. Ce n'est généralement pas un gros problème, mais si la configuration de la variable d'instance prend du temps, vous perdez des cycles. Pour la méthode définie parlet
, le code d'initialisation ne s'exécute que si l'exemple l'appelle.@
).let
et de garder monit
bloc agréable et court.Un lien connexe peut être trouvé ici: http://www.betterspecs.org/#let
la source
let
pour définir tous les objets dépendants, et de l'utiliserbefore(:each)
pour configurer la configuration requise ou tout faux / stubs requis par les exemples. Je préfère cela à un grand crochet avant contenant tout cela. Aussi,let(:foo) { Foo.new }
c'est moins bruyant (et plus pertinent) alorsbefore(:each) { @foo = Foo.new }
. Voici un exemple de la façon dont je l'utilise: github.com/myronmarston/vcr/blob/v1.7.0/spec/vcr/util/…NoMethodError
un avertissement, mais YMMV.foo = Foo.new(...)
et ensuite des utilisateursfoo
sur des lignes ultérieures. Plus tard, vous écrivez un nouvel exemple dans le même groupe d'exemples qui nécessite également uneFoo
instanciation de la même manière. À ce stade, vous souhaitez refactoriser pour éliminer la duplication. Vous pouvez supprimer lesfoo = Foo.new(...)
lignes de vos exemples et les remplacer par unlet(:foo) { Foo.new(...) }
sans changer la façon dont les exemples sont utilisésfoo
. Mais si vous refactorisez,before { @foo = Foo.new(...) }
vous devez également mettre à jour les références dans les exemples defoo
à@foo
.La différence entre l'utilisation de variables d'instance et
let()
celle quilet()
est évaluée paresseusement . Cela signifie quelet()
n'est pas évalué tant que la méthode qu'il définit n'est pas exécutée pour la première fois.La différence entre
before
etlet
est que celalet()
vous donne une belle façon de définir un groupe de variables dans un style «en cascade». Ce faisant, la spécification est un peu meilleure en simplifiant le code.la source
let
si vous avez besoin que quelque chose soit évalué à chaque fois? Par exemple, j'ai besoin qu'un modèle enfant soit présent dans la base de données avant qu'un comportement ne se déclenche sur le modèle parent. Je ne fais pas nécessairement référence à ce modèle enfant dans le test, car je teste le comportement des modèles parents. Pour le moment, j'utilise lalet!
méthode à la place, mais peut-être qu'il serait plus explicite de mettre cette configurationbefore(:each)
?J'ai complètement remplacé toutes les utilisations des variables d'instance dans mes tests rspec pour utiliser let (). J'ai écrit un exemple rapide pour un ami qui l'a utilisé 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 donc il ne chargera que celles qui nécessitent un chargement. Il sèche la spécification et la rend plus lisible. J'ai en fait porté le code Rspec let () à utiliser dans mes contrôleurs, dans le style de gem hérité_ressource. http://ruby-lambda.blogspot.com/2010/06/stealing-let-from-rspec.html
Avec l'évaluation paresseuse, l'autre avantage est que, combiné à ActiveSupport :: Concern, et à la spécification / support / comportement tout-en-un, vous pouvez créer votre propre mini-DSL de spécification spécifique à votre application. J'ai écrit des tests pour les ressources Rack et RESTful.
La stratégie que j'utilise est Factory-everything (via Machinist + Forgery / Faker). Cependant, il est possible de l'utiliser en combinaison avec des blocs avant (: chaque) pour précharger les usines pour un ensemble complet de groupes d'exemples, ce qui permet aux spécifications de s'exécuter plus rapidement: http://makandra.com/notes/770-taking-advantage -of-rspec-s-let-in-before-blocks
la source
# spec/friendship_spec.rb
et# spec/comment_spec.rb
exemple, ne pensez-vous pas qu'ils le rendent moins lisible? Je n'ai aucune idée d'oùusers
viennent et devront creuser plus profondément.let()
les deux derniers jours et personnellement, je ne vois pas de différence, à l'exception du premier avantage mentionné par Myron. Et je ne suis pas sûr de lâcher prise et de ne pas le faire, peut-être parce que je suis paresseux et j'aime voir le code à l'avance sans avoir à ouvrir encore un autre fichier. Merci pour vos commentaires.Il est important de garder à l'esprit que let est évalué paresseux et ne pas y mettre de méthodes d'effets secondaires sinon vous ne pourriez pas passer facilement de let à avant (: chaque) . Vous pouvez utiliser let! au lieu de laisser afin qu'il soit évalué avant chaque scénario.
la source
En général,
let()
c'est une syntaxe plus agréable, et cela vous évite de taper des@name
symboles partout. Mais, caveat emptor! J'ai trouvélet()
aussi introduit des bugs subtils (ou au moins gratter la tête) , car la variable n'existe pas vraiment jusqu'à ce que vous essayez de l' utiliser ... Dites signe de conte: si l' ajout d' unputs
aprèslet()
pour voir que la variable est correcte permet une spécification pour passer, mais sans laputs
spécification échoue - vous avez trouvé cette subtilité.J'ai également constaté que
let()
cela ne semble pas en cache en toutes circonstances! Je l'ai écrit dans mon blog: http://technicaldebt.com/?p=1242C'est peut-être juste moi?
la source
let
mémorise toujours la valeur pour la durée d'un seul exemple. Il ne mémorise pas la valeur dans plusieurs exemples.before(:all)
, en revanche, vous permet de réutiliser une variable initialisée dans plusieurs exemples.let!
pour cela. relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/…Let est fonctionnel car c'est essentiellement un Proc. Aussi son caché.
Un problème que j'ai trouvé tout de suite avec let ... Dans un bloc Spec qui évalue un changement.
Vous devez être sûr d'appeler en
let
dehors de votre bloc d'attente. c'est-à-dire que vous appelezFactoryGirl.create
dans votre bloc let. Je le fais généralement en vérifiant que l'objet est persistant.Sinon, lorsque le
let
bloc est appelé la première fois, un changement dans la base de données se produit en raison de l'instanciation paresseuse.Mise à jour
Il suffit d'ajouter une note. Soyez prudent en jouant au golf à code ou dans ce cas au golf rspec avec cette réponse.
Dans ce cas, je n'ai qu'à appeler une méthode à laquelle l'objet répond.
_.persisted?
J'invoque donc la méthode _ sur l'objet comme étant véridique. Tout ce que j'essaie de faire, c'est d'instancier l'objet. Vous pourriez appeler vide? ou nul? aussi. Le point n'est pas le test mais donner vie à l'objet en l'appelant.Vous ne pouvez donc pas refactoriser
être
comme l'objet n'a pas été instancié ... c'est paresseux. :)
Update 2
tirer parti de la laisser! syntaxe pour la création instantanée d'objets, ce qui devrait éviter complètement ce problème. Notez bien que cela ira à l'encontre de l'objectif de la paresse du let non frappé.
De plus, dans certains cas, vous souhaiterez peut-être utiliser la syntaxe du sujet au lieu de la laisser car cela peut vous donner des options supplémentaires.
la source
Remarque à Joseph - si vous créez des objets de base de données dans un,
before(:all)
ils ne seront pas capturés dans une transaction et vous êtes beaucoup plus susceptible de laisser des fichiers dans votre base de données de test. Utilisezbefore(:each)
plutôt.L'autre raison d'utiliser let et son évaluation paresseuse est que vous puissiez prendre un objet compliqué et tester des pièces individuelles en remplaçant let dans des contextes, comme dans cet exemple très artificiel:
la source
"avant" par défaut implique
before(:each)
. Réf The Rspec Book, copyright 2010, page 228.J'utilise
before(:each)
pour amorcer des données pour chaque groupe d'exemple sans avoir à appeler lalet
méthode 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 dans 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 savoir plus à ce sujet ici: https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-letla source
Voix dissidente ici: après 5 ans de rspec je n'aime pas
let
trop.1. L'évaluation paresseuse rend souvent la configuration du test confuse
Il devient difficile de raisonner sur la configuration lorsque certaines choses qui ont été déclarées dans la configuration n'affectent pas réellement l'état, tandis que d'autres le sont.
Finalement, de quelqu'un de frustration change juste
let
àlet!
(même chose sans évaluation paresseuse) afin d'obtenir leur travail spec. Si cela fonctionne pour eux, une nouvelle habitude est née: lorsqu'une nouvelle spécification est ajoutée à une suite plus ancienne et que cela ne fonctionne pas, la première chose que l'écrivain essaie est d'ajouter une frange auxlet
appels aléatoires .Bientôt, tous les avantages de performance ont disparu.
2. La syntaxe spéciale est inhabituelle pour les utilisateurs non-rspec
Je préfère enseigner Ruby à mon équipe que les astuces de rspec. Les variables d'instance ou les appels de méthode sont utiles partout dans ce projet et dans d'autres, la
let
syntaxe ne sera utile que dans rspec.3. Les "avantages" nous permettent d'ignorer facilement les bonnes modifications de conception
let()
est bon pour les dépendances coûteuses que nous ne voulons pas créer encore et encore. Il s'associe également bien avecsubject
, vous permettant de sécher les appels répétés aux méthodes multi-argumentsDes dépendances coûteuses répétées plusieurs fois et des méthodes avec de grandes signatures sont deux points où nous pourrions améliorer le code:
Dans tous ces cas, je peux traiter le symptôme de tests difficiles avec un baume apaisant de magie rspec, ou je peux essayer de traiter la cause. J'ai l'impression d'avoir passé beaucoup trop de temps ces dernières années sur le premier et maintenant je veux un meilleur code.
Pour répondre à la question d'origine: je préférerais ne pas le faire, mais j'utilise toujours
let
. Je l' utilise principalement pour m'adapter au style du reste de l'équipe (il semble que la plupart des programmeurs Rails dans le monde soient maintenant profondément plongés dans leur magie rspec, ce qui est très souvent). Parfois, je l'utilise lorsque j'ajoute un test à un code que je n'ai pas le contrôle ou que je n'ai pas le temps de refactoriser pour une meilleure abstraction: c'est-à-dire lorsque la seule option est l'analgésique.la source
j'utilise
let
pour tester mes réponses HTTP 404 dans mes spécifications API en utilisant des contextes.Pour créer la ressource, j'utilise
let!
. Mais pour stocker l'identifiant de ressource, j'utiliselet
. Regardez à quoi ça ressemble:Cela maintient les spécifications propres et lisibles.
la source