RSpec: Quelle est la différence entre let et un bloc avant?

90

Quelle est la différence entre letet un beforebloc dans RSpec?

Et quand les utiliser?

Quelle sera la bonne approche (laissez ou avant) dans l'exemple ci-dessous?

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

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

J'ai étudié ce post de stackoverflow

Mais est-il bon de définir des trucs d'association comme ci-dessus?

Kriysna
la source
1
Fondamentalement, «let» est utilisé par les personnes qui n'aiment pas les variables d'instance. En remarque, vous devriez envisager d'utiliser FactoryGirl ou un outil similaire.
apneadiving

Réponses:

146

Les gens semblent avoir expliqué certaines des façons fondamentales dont ils diffèrent, mais les ont laissés de côté before(:all)et n'expliquent pas exactement pourquoi ils devraient être utilisés.

Je pense que les variables d'instance n'ont pas leur place dans la grande majorité des spécifications, en partie pour les raisons mentionnées dans cette réponse , je ne les mentionnerai donc pas ici.

laissez les blocs

Le code à l'intérieur d'un letbloc n'est exécuté que lorsqu'il est référencé, le chargement différé signifie que l'ordre de ces blocs n'est pas pertinent. Cela vous donne une grande quantité d'énergie pour réduire les configurations répétées grâce à vos spécifications.

Un exemple (extrêmement petit et artificiel) de ceci est:

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

Vous pouvez voir que cela has_moustacheest défini différemment dans chaque cas, mais il n'est pas nécessaire de répéter la subjectdéfinition. Il est important de noter que le dernier letbloc défini dans le contexte actuel sera utilisé. C'est bon pour définir une valeur par défaut à utiliser pour la majorité des spécifications, qui peuvent être écrasées si nécessaire.

Par exemple, vérifier la valeur de retour de calculate_awesomeif passé un personmodèle avec top_hatla valeur true, mais aucune moustache ne serait:

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

Une autre chose à noter à propos des blocs let, ils ne doivent pas être utilisés si vous recherchez quelque chose qui a été sauvegardé dans la base de données (c'est-à-dire Library.find_awesome_people(search_criteria)) car ils ne seront pas sauvegardés dans la base de données à moins qu'ils n'aient déjà été référencés. let!ou des beforeblocs sont ce qui devrait être utilisé ici.

Aussi, ne l' utilisez jamaisbefore pour déclencher l'exécution de letblocs, c'est pour cela qu'il let!est fait!

laisser! blocs

let!les blocs sont exécutés dans l'ordre dans lequel ils sont définis (un peu comme un bloc avant). La seule différence fondamentale avec les blocs avant est que vous obtenez une référence explicite à cette variable, plutôt que de devoir revenir aux variables d'instance.

Comme pour les letblocs, si plusieurs let!blocs sont définis avec le même nom, le plus récent est celui qui sera utilisé lors de l'exécution. La principale différence est que les let!blocs seront exécutés plusieurs fois s'ils sont utilisés de cette manière, alors que le letbloc ne s'exécutera que la dernière fois.

avant (: chaque) blocs

before(:each)est le bloc par défaut avant, et peut donc être référencé comme before {}plutôt que de spécifier le plein à before(:each) {}chaque fois.

C'est ma préférence personnelle d'utiliser des beforeblocs dans quelques situations fondamentales. J'utiliserai avant les blocs si:

  • J'utilise des moqueries, des stubbing ou des doubles
  • Il y a une configuration de taille raisonnable (généralement, c'est un signe que vos caractéristiques d'usine n'ont pas été configurées correctement)
  • Il y a un certain nombre de variables que je n'ai pas besoin de référencer directement, mais qui sont requises pour la configuration
  • J'écris des tests de contrôleurs fonctionnels dans des rails, et je souhaite exécuter une demande spécifique de test (ie before { get :index }). Même si vous pouvez l'utiliser subjectdans de nombreux cas, cela semble parfois plus explicite si vous n'avez pas besoin d'une référence.

Si vous vous retrouvez à écrire de gros beforeblocs pour vos spécifications, vérifiez vos usines et assurez-vous de bien comprendre les caractéristiques et leur flexibilité.

avant (: tous) blocs

Celles-ci ne sont exécutées qu'une seule fois, avant les spécifications dans le contexte actuel (et ses enfants). Ceux-ci peuvent être utilisés à bon escient s'ils sont écrits correctement, car dans certaines situations, cela peut réduire l'exécution et les efforts.

Un exemple (qui n'affecterait guère le temps d'exécution) consiste à simuler une variable ENV pour un test, ce que vous ne devriez jamais avoir à faire qu'une seule fois.

J'espère que cela pourra aider :)

Geai
la source
Pour les utilisateurs minitest, ce que je suis mais je n'ai pas remarqué la balise RSpec de ce Q&A, l' before(:all)option n'existe pas dans Minitest. Voici une solution de contournement dans les commentaires: github.com/seattlerb/minitest/issues/61
MrYoshiji
L'article référencé est un lien mort: \
Dylan Pierce
Merci @DylanPierce. Je ne trouve aucune copie de cet article, j'ai donc référencé une réponse SO qui aborde ce problème à la place :)
Jay
Merci :) très apprécié
Dylan Pierce
1
itsn'est plus dans rspec-core. Une manière plus moderne et idiomatique est it { is_expected.to be_awesome }.
Zubin
26

Presque toujours, je préfère let. Le message que vous liez spécifie qu'il letest également plus rapide. Cependant, parfois, lorsque de nombreuses commandes doivent être exécutées, je pourrais l'utiliser before(:each)car sa syntaxe est plus claire lorsque de nombreuses commandes sont impliquées.

Dans votre exemple, je préférerais certainement utiliser letau lieu de before(:each). De manière générale, quand une simple initialisation de variable est effectuée, j'ai tendance à aimer utiliser let.

Spyros
la source
15

Une grande différence qui n'a pas été mentionnée est que les variables définies avec letne sont instanciées que lorsque vous l'appelez pour la première fois. Ainsi, même si un before(:each)bloc instancie toutes les variables, letvous permet de définir un certain nombre de variables que vous pourriez utiliser dans plusieurs tests, il ne les instancie pas automatiquement. Sans le savoir, vos tests pourraient revenir vous mordre si vous vous attendez à ce que toutes les données soient chargées à l'avance. Dans certains cas, vous pouvez même définir un certain nombre de letvariables, puis utiliser un before(:each)bloc pour appeler chaque letinstance juste pour vous assurer que les données sont disponibles pour commencer.

mikeweber
la source
20
Vous pouvez utiliser let!pour définir des méthodes appelées avant chaque exemple. Voir la documentation RSpec .
Jim Stewart
3

Il semble que vous utilisiez Machinist. Attention, vous pouvez rencontrer des problèmes avec make! à l'intérieur de let (la version non-bang) se produisant en dehors de la transaction globale de fixture (si vous utilisez également des fixtures transactionnelles) corrompant ainsi les données pour vos autres tests.

Tim Connor
la source