Le deuxième article de cette courte série vous apprend à utiliser les différents agents de correspondance fournis avec RSpec. Il vous explique également comment découper votre suite de tests en balisage, comment fonctionnent les rappels et comment extraire certaines données. Nous développons un peu le kit de survie de base du premier article et nous vous montrons assez pour être dangereux sans trop de corde pour se pendre.
Dans le premier article, nous avons passé beaucoup de temps à essayer de répondre au «pourquoi?» Des tests. Je suggère que nous revenions au «comment?» Et que nous nous épargnions davantage de contexte. Nous avons déjà largement couvert cette partie. Voyons ce que RSpec a à offrir et que vous pouvez gérer immédiatement en tant que débutant..
Donc, cela va aborder le cœur des choses. RSpec vous fournit une tonne de soi-disant matchers. Ce sont votre pain et le beurre lorsque vous écrivez vos attentes. Jusqu'à présent, vous avez vu .à eq
et .not_to eq
. Mais il existe un arsenal beaucoup plus grand pour écrire vos spécifications. Vous pouvez tester pour générer des erreurs, des valeurs de vérité et de fausseté, ou même des classes spécifiques. Voyons quelques options pour vous aider à démarrer:
.à eq
.not_to eq
Ceci teste pour l'équivalence.
… C'est 'une description intelligente' à attendre (agent.enemy) .à eq 'Ernst Stavro Blofeld' à attendre (agent.enemy) .not_to eq 'Winnie Pooh' à la fin…
Pour faire court, j’ai emballé deux déclarations d’attente dans une il
bloc. Il est toutefois bon de ne tester qu'une seule chose par test. Cela garde les choses beaucoup plus concentrées et vos tests seront moins fragiles lorsque vous changerez les choses..
.être_truthy
.pour être vrai
… C'est 'une description intelligente' attendez-vous (agent.hero?). Soyez_truthy attendez-vous (ennemi.mégalomane?). Pour être la vraie fin…
La différence est que be_truthy
est vrai quand ce n'est pas néant
ou faux
. Donc cela passera si le résultat n’est ni l’un ni l’autre de ces deux types de «vrais». .pour être vrai
d'autre part n'accepte qu'une valeur qui est vrai
et rien d'autre.
.être_falsy
.être faux
... c'est une "description intelligente" à attendre (agent.coward?). À être_falsy à attendre (ennemi.mégalomane?). À être une fin fausse ...
Similaire aux deux exemples ci-dessus, .être_falsy
attend soit un faux
ou un néant
valeur, et .être faux
fera seulement une comparaison directe sur faux
.
.être_nil
.to_not be_nil
Et last but not least, cela teste exactement pour néant
lui-même. Je vous épargne l'exemple.
.correspondre()
J'espère que vous avez déjà eu le plaisir d'examiner les expressions régulières. Si ce n'est pas le cas, il s'agit d'une séquence de caractères avec laquelle vous pouvez définir un motif que vous insérez entre deux barres obliques pour rechercher des chaînes. Une expression régulière peut être très utile si vous voulez rechercher des motifs plus larges que vous pourriez généraliser dans une telle expression..
… C'est 'une description intelligente' attendez-vous (agent.numéro.to_i) .pour faire correspondre (/ \ d 3 /) fin…
Supposons que nous ayons affaire à des agents comme James Bond, 007, auxquels sont attribués des numéros à trois chiffres. Ensuite, nous pourrions le tester de cette façon-primitivement ici, bien sûr.
>
<
<=
> =
Les comparaisons sont plus utiles qu'on ne le pense. Je suppose que les exemples ci-dessous couvriront ce que vous devez savoir.
… C'est une «description intelligente»… attendez-vous (numéro d'agent). < quartermaster.number expect(agent.number).to be > m.number expect (agent.kill_count) .to> = 25 expect (quartermaster.number_of_gadgets) .to be <= 5 end…
Maintenant, nous obtenons un endroit moins ennuyeux. Vous pouvez également tester des classes et des types:
.être_une_instance_de
.être un
.être un
… C'est 'une description intelligente' faire mission = mission.create (nom: 'Moonraker') agent = agent.create (nom: 'James Bond') mission.agents << agent expect(@mission.agents).not_to be_an_instance_of(Agent) expect(@mission.agents).to be_a(ActiveRecord::Associations::CollectionProxy) end…
Dans l'exemple factice ci-dessus, vous pouvez voir qu'une liste d'agents associés à une mission n'est pas de classe. Agent
mais de ActiveRecord :: Associations :: CollectionProxy
. Ce que vous devriez retenir de celui-ci est que nous pouvons facilement tester les cours eux-mêmes tout en restant très expressifs.. .être un
et .être un
faire la même chose. Vous avez les deux options disponibles pour garder les choses lisibles.
Le test des erreurs est également extrêmement pratique dans RSpec. Si vous êtes vraiment à l'aise avec Rails et que vous ne savez pas encore quelles erreurs le framework peut vous causer, vous pourriez ne pas ressentir le besoin de les utiliser - bien sûr, c'est tout à fait logique. Cependant, à un stade ultérieur de votre développement, vous les trouverez très utiles. Vous avez quatre moyens de les gérer:
.lever_erreur
C'est la manière la plus générique. Quelle que soit l'erreur générée, elle sera lancée dans votre filet..
.to raise_error (ErrorClass)
De cette façon, vous pouvez spécifier exactement de quelle classe l'erreur doit provenir.
.to raise_error (ErrorClass, "Un certain message d'erreur")
C'est encore plus fin puisque vous ne mentionnez pas seulement la classe de l'erreur mais un message spécifique qui devrait être jeté avec l'erreur.
.to raise_error ("Un message d'erreur)
Ou vous venez de mentionner le message d'erreur lui-même sans la classe d'erreur. La partie attendue doit être écrite un peu différemment, bien que nous devions envelopper la partie sous le texte dans un bloc de code lui-même:
… Il s'agit d'une «description intelligente» agent = Agent.create (nom: 'James Bond'), attendez agent.lady_killer?. Sur_error (NoMethodError), attendez double_agent.name. name .to raise_error ("Erreur: aucun agent double autour") attend double_agent.name .to raise_error (NameError, "Erreur: aucun agent double autour") se termine ...
.commencer avec
.pour finir avec
Comme nous utilisons souvent des collections lors de la création d'applications Web, il est agréable de disposer d'un outil pour les consulter. Ici, nous avons ajouté deux agents, Q et James Bond, et nous voulions juste savoir qui venait en premier et dernier dans la collection d'agents pour une mission particulière, ici Moonraker..
… C'est 'une description intelligente' de Moonraker = Mission.create (nom: 'Moonraker') bond = Agent.create (nom: 'James Bond') q = Agent.create (nom: 'Q') moonraker.agents << bond moonraker.agents << q expect(moonraker.agents).to start_with(bond) expect(moonraker.agents).to end_with(q) end…
.inclure
Celui-ci est également utile pour vérifier le contenu des collections.
… C'est 'une description intelligente' faire mission = mission.create (nom: 'Moonraker') bond = Agent.create (nom: 'James Bond') mission.agents << bond expect(mission.agents).to include(bond) end…
RSpec propose ces correspondeurs de prédicats pour créer dynamiquement des correspondeurs pour vous. Si vous avez des méthodes de prédicat dans vos modèles, par exemple (se terminant par un point d'interrogation), alors RSpec sait qu'il doit créer des adaptateurs que vous pouvez utiliser dans vos tests. Dans l'exemple ci-dessous, nous voulons tester si un agent est James Bond:
agent de classe < ActiveRecord::Base def bond? name == 'James Bond' && number == '007' && gambler == true end… end
Maintenant, nous pouvons utiliser ceci dans nos spécifications comme ceci:
… Il 'une description intelligente' agent = Agent.create (nom: 'James Bond', numéro: '007', joueur: vrai) attendez (agent). Pour être_bond la fin 'une description intelligente' agent = Agent. créez (nom: 'James Bond') attendez (agent). not_to be_bond end…
RSpec nous permet d'utiliser le nom de la méthode sans le point d'interrogation pour former une meilleure syntaxe, je suppose. Cool, n'est-ce pas?
laisser
et laisser!
peut ressembler à des variables au début, mais ce sont en fait des méthodes auxiliaires. Le premier est évalué paresseusement, ce qui signifie qu'il n'est exécuté et évalué que lorsqu'une spécification l'utilise réellement, et le second laissé avec le bang (!) Est exécuté, qu'il soit utilisé par une spécification ou non. Les deux versions sont mémoisées et leurs valeurs seront mises en cache dans le même exemple.
décris Mission, '#prepare',: let let (: mission) Mission.create (nom: 'Moonraker') let! (: bond) Agent.create (nom: 'James Bond') ajoute ' agents à une mission 'do mission.prepare (lien) attendre (mission.agents) .pour inclure lien end end
La version bang qui n’est pas évaluée paresseusement peut prendre beaucoup de temps et donc coûter cher si elle devient votre nouvel ami à la mode. Pourquoi? Parce qu'il va configurer ces données pour chaque test en question, quoi qu'il arrive, et pourrait éventuellement devenir l'une de ces choses désagréables qui ralentissent considérablement votre suite de tests.
Vous devriez connaître cette fonctionnalité de RSpec depuis laisser
est largement connu et utilisé. Cela étant dit, le prochain article vous montrera quelques problèmes dont vous devriez être au courant. Utilisez ces méthodes auxiliaires avec prudence, ou du moins à petites doses pour le moment..
RSpec vous offre la possibilité de déclarer le sujet sous test de manière très explicite. Il existe de meilleures solutions pour cela, et nous discuterons des inconvénients de cette approche dans le prochain article lorsque je montrerai quelques points que vous souhaitez généralement éviter. Mais pour l'instant, regardons ce que assujettir
peut faire pour vous:
décrire Agent, '#status' ne soumet-t-il pas Agent.create (nom: 'Bond') il renvoie le statut des agents 'ne s’attend pas (subject.status). ne peut pas être' MIA 'end end
Cette approche peut, d’une part, vous aider à réduire la duplication de code, en ayant un protagoniste déclaré une fois dans un certain périmètre, mais elle peut également conduire à quelque chose appelé un invité mystère. Cela signifie simplement que nous risquons de nous retrouver dans une situation où nous utilisons des données pour l'un de nos scénarios de test sans avoir la moindre idée de leur origine ou de leur nature. Plus à ce sujet dans le prochain article.
Au cas où vous ne seriez pas au courant des rappels, laissez-moi vous donner un bref aperçu. Les rappels sont exécutés à certains moments spécifiques du cycle de vie du code. En termes de Rails, cela signifie que du code est en cours d’exécution avant que les objets ne soient créés, mis à jour, détruits, etc..
Dans le contexte de RSpec, il s'agit du cycle de vie des tests en cours d'exécution. Cela signifie simplement que vous pouvez spécifier les points d'ancrage à exécuter avant ou après chaque test dans le fichier de spécifications, par exemple, ou simplement autour de chaque test. Il existe encore quelques options plus fines, mais je vous recommande de ne pas vous perdre dans les détails pour le moment. Premières choses d'abord:
avant (: chaque)
Ce rappel est exécuté avant chaque exemple de test.
décrire Agent, '#favorite_gadget' faire avant (: each) do @gagdet = Gadget.create (nom: 'Walther PPK') end it 'renvoie un élément, le gadget favori de l'agent' do agent = Agent.create (nom : 'James Bond') agent.favorite_gadgets << @gadget expect(agent.favorite_gadget).to eq 'Walther PPK' end… end
Supposons que vous ayez besoin d'un gadget spécifique pour chaque test exécuté dans une certaine étendue.. avant
vous permet d'extraire ceci dans un bloc et prépare ce petit extrait pour vous commodément. Lorsque vous configurez les données de cette manière, vous devez bien sûr utiliser des variables d'instance pour y accéder entre différentes portées..
Ne vous fiez pas à la commodité dans cet exemple. Ce n’est pas parce que vous pouvez faire ce genre de choses que vous devriez le faire. Je veux éviter d'aller sur le territoire de l'AntiPattern et vous troubler, mais d'un autre côté, je veux expliquer un peu les inconvénients de ces simples exercices factices.
Dans l'exemple ci-dessus, il serait beaucoup plus expressif de configurer les objets nécessaires test par test. Surtout sur des fichiers de spécifications plus volumineux, vous pouvez rapidement perdre de vue ces petites connexions et rendre plus difficile la tâche des autres de reconstituer ces puzzles..
avant tout)
Ce avant
block s'exécute une seule fois avant tous les autres exemples d'un fichier de spécification.
décrire Agent, '#enemy' faire avant (: tous) faire @main_villain = Villain.create (nom: 'Ernst Stavro Blofeld') @mission = Mission.create (nom: 'Moonraker') @ mission.villains << @main_villain end it 'returns the main enemy Bond has to face in his mission' do agent = Agent.create(name: 'James Bond') @mission.agents << agent expect(agent.enemy).to eq 'Ernst Stavro Blofeld' end… end
Quand vous vous souvenez des quatre phases du test, avant
les blocs sont parfois utiles pour vous préparer quelque chose qui doit être répété régulièrement - probablement des trucs un peu plus méta dans la nature.
après chaque)
et après tout)
ont le même comportement mais sont simplement exécutés après l'exécution de vos tests. après
est souvent utilisé pour nettoyer vos fichiers, par exemple. Mais je pense qu'il est un peu tôt pour résoudre ce problème. Alors, mettez-le en mémoire, sachez que c'est là pour le cas où vous en auriez besoin, et passons à autre chose..
Tous ces rappels peuvent être placés de manière stratégique pour répondre à vos besoins. Placez-les dans décrire
bloquer la portée que vous devez exécuter - ils ne doivent pas nécessairement être placés au-dessus de votre fichier de spécifications. Ils peuvent facilement être imbriqués dans vos spécifications.
décrire l'agent faire avant (: chaque) faire @mission = mission.create (nom: 'Moonraker') @bond = agent.create (nom: 'James Bond', numéro: '007') et décrire '#enemy' avant (@ each) do @main_villain = Villain.create (nom: 'Ernst Stavro Blofeld') @ mission.villains << @main_villain end describe 'Double 0 Agent with associated mission' do it 'returns the main enemy the agent has to face in his mission' do @mission.agents << @bond expect(@bond.enemy).to eq 'Ernst Stavro Blofeld' end end describe 'Low-level agent with associated mission' do it 'returns no info about the main villain involved' do some_schmuck = Agent.create(name: 'Some schmuck', number: '1024') @mission.agents << some_schmuck expect(some_schmuck.enemy).to eq 'That's above your paygrade!' end end… end end
Comme vous pouvez le constater, vous pouvez placer des blocs de rappel à n’importe quelle portée et aller aussi loin que vous le souhaitez. Le code dans le rappel sera exécuté dans la portée de toute portée de bloc décrit. Mais un petit conseil: si vous ressentez le besoin de trop imbriquer et que les choses semblent devenir un peu compliquées et compliquées, repensez votre approche et réfléchissez à la façon de simplifier les tests et leur configuration. BAISER! Restez simple, stupide. Aussi, faites attention à la façon dont cela se lit bien lorsque nous forçons ces tests à échouer:
Échecs: 1) Agent # ennemi Double 0 L'agent associé à la mission renvoie l'ennemi principal auquel l'agent doit faire face dans sa mission Échec / Erreur: attendez (@ bond.enemy) .vers l'eq 'Ernst Stavro Blofeld' attendu: "Ernst Stavro Blofeld "got:" Blofeld "2) Agent # ennemi Un agent de niveau inférieur avec la mission associée ne renvoie aucune information sur le principal méchant impliqué. Échec / Erreur: attendez-vous à (some_schmuck.enemy) .to eq" C'est au-dessus de votre niveau de paiement! " attendu: "C'est au-dessus de votre niveau de rémunération!" a obtenu: "Blofeld"
Jetons également un coup d’œil sur les générateurs fournis par RSpec pour vous. Vous avez déjà vu un lorsque nous avons utilisé les rails génèrent rspec: install
. Ce petit gars a fait installer RSpec pour nous rapidement et facilement. Qu'avons-nous d'autre?
rspec: modèle
Vous voulez avoir une autre spécification de modèle factice?
les rails génèrent rspec: model another_dummy_model
create spec / models / another_dummy_model_spec.rb
Vite, n'est ce pas? Ou une nouvelle spécification pour un test de contrôleur, par exemple:
rspec: contrôleur
les rails génèrent rspec: contrôleur dummy_controller
spec / controllers / dummy_controller_controller_spec.rb
rspec: voir
La même chose fonctionne pour les vues, bien sûr. Nous ne testerons pas de telles vues, cependant. Les spécifications relatives aux vues vous en donnent le minimum, et il est probablement suffisant dans presque tous les scénarios de tester indirectement vos vues via des tests de fonctionnalités..
Les tests de fonctionnalité ne sont pas une spécialité de RSpec en soi et sont plus adaptés à un autre article. Cela dit, si vous êtes curieux, jetez un œil à Capybara, qui est un excellent outil pour ce genre de chose. Il vous permet de tester des flux entiers qui utilisent plusieurs parties de votre application pour tester ensemble des fonctionnalités complètes tout en simulant l'expérience du navigateur. Par exemple, un utilisateur qui paie plusieurs articles dans un panier.
rspec: assistant
La même stratégie de générateur nous permet également de placer un assistant sans grande difficulté.
les rails génèrent rspec: helper dummy_helper
créer spec / helpers / dummy_helper_helper_spec.rb
Le double helper_helper
la partie n'était pas un accident. Quand nous lui donnerons un nom plus “significatif”, vous verrez que RSpec attache seulement _assistant
seul.
les rails génèrent rspec: helper important_stuff
créer spec / helpers / important_stuff_helper_spec.rb
Non, ce répertoire n’est pas un lieu de stockage pour vos précieuses méthodes d’aide lors du refactoring de vos tests. Ceux-ci iraient sous spec / support
, réellement. spec / aides
est pour les tests que vous devriez écrire pour vos assistants de vue, un assistant comme régler la date
serait un exemple commun. Oui, la couverture de test complète de votre code devrait également inclure ces méthodes auxiliaires. Ce n’est pas parce qu’ils semblent souvent petits et triviaux que nous devons les ignorer ou ignorer leur potentiel de bugs que nous voulons attraper. Plus l'assistant s'avère complexe, plus vous devriez avoir à écrire un helper_spec
pour ça!
Juste au cas où vous commenceriez à vous en occuper immédiatement, gardez à l’esprit que vous devez exécuter vos méthodes d’aide de manière autonome. assistant
objet lorsque vous écrivez vos tests d'assistance afin de travailler. Donc, ils ne peuvent être exposés qu'en utilisant cet objet. Quelque chose comme ça:
décrivez '#set_date' do… helper.set_date… end…
Vous pouvez utiliser le même type de générateurs pour les spécifications de fonctionnalité, les spécifications d'intégration et les spécifications de courrier. Celles-ci sont hors de notre portée pour aujourd'hui, mais vous pouvez les enregistrer en mémoire pour une utilisation future:
Les spécifications que nous avons créées via le générateur ci-dessus sont prêtes à l'emploi et vous pouvez y ajouter immédiatement vos tests. Voyons un peu la différence entre les spécifications:
require 'rails_helper' RSpec.describe DummyModel, tapez:: model do en attente "ajouter des exemples à (ou supprimer) # __ FILE__" end
require 'rails_helper' RSpec.describe DummyControllerController, tapez:: controller do end
require 'rails_helper' RSpec.describe DummyHelperHelper, tapez:: helper do en attente "ajouter des exemples à (ou supprimer) # __ FILE__" end
Il n’est pas nécessaire que les gens fassent le nécessaire pour comprendre qu’ils ont tous des types différents. Ce :type
Les métadonnées RSpec vous donnent l’opportunité de découper vos tests dans des structures de fichiers. Vous pouvez ainsi mieux cibler ces tests. Imaginons par exemple que vous souhaitiez charger une sorte d’aide uniquement pour les spécifications du contrôleur. Un autre exemple serait que vous souhaitiez utiliser une autre structure de répertoires pour des spécifications que RSpec n'attend pas. Avoir ces métadonnées dans vos tests permet de continuer à utiliser les fonctions de support de RSpec et de ne pas déclencher la suite de tests. Vous êtes donc libre d’utiliser la structure de répertoires qui vous convient si vous ajoutez ceci. :type
métadonnées.
Par contre, vos tests RSpec standard ne dépendent pas de ces métadonnées. Lorsque vous utilisez ces générateurs, ils sont ajoutés gratuitement, mais vous pouvez également les éviter totalement si vous n'en avez pas besoin..
Vous pouvez également utiliser ces métadonnées pour filtrer vos spécifications. Supposons que vous ayez un bloc avant qui ne devrait être exécuté que sur les spécifications de modèle, par exemple. Soigné! Pour les plus grandes suites de tests, cela peut s'avérer très utile un jour. Vous pouvez filtrer le groupe de tests ciblé que vous souhaitez exécuter, au lieu d'exécuter toute la suite, ce qui peut prendre un certain temps..
Vos options vont bien au-delà des trois options de marquage ci-dessus. Apprenons-en plus sur le découpage et la découpe de vos tests dans la section suivante..
Lorsque vous accumulez une suite de tests plus volumineuse au fil du temps, il ne suffit pas d'exécuter des tests dans certains dossiers pour exécuter les tests RSpec rapidement et efficacement. Ce que vous voulez être capable de faire, c'est d'exécuter des tests qui vont de pair, mais peuvent être répartis sur plusieurs annuaires. Le marquage à la rescousse! Comprenez-moi bien, organiser vos tests intelligemment dans vos dossiers est également essentiel, mais le balisage va un peu plus loin..
Vous donnez à vos tests des métadonnées sous forme de symboles tels que “: wip”, “: checkout” ou tout ce qui vous convient. Lorsque vous exécutez ces groupes de tests ciblés, vous indiquez simplement que RSpec doit ignorer l'exécution d'autres tests cette fois en fournissant un indicateur avec le nom des balises..
Décrivez Agent: wip do it 'est un désordre en ce moment' attendez-vous (agent.favorite_gadgets) .to eq 'Unknown' end end
rspec --tag wip
Échecs: 1) L’agent est dans un état désastreux en ce moment. Échec / Erreur: attendez (agent.favorite_gadgets) .to eq 'Unknown'…
Vous pouvez également exécuter toutes sortes de tests et ignorer un groupe de groupes étiquetés d'une certaine manière. Vous venez de fournir un tilde (~) devant le nom de la balise, et RSpec se fera un plaisir d’ignorer ces tests..
rspec --tag ~ wip
L'exécution simultanée de plusieurs balises n'est pas un problème non plus:
rspec --tag wip --tag checkout rspec --tag ~ wip --tag checkout
Comme vous pouvez le voir ci-dessus, vous pouvez les mélanger et les assortir à volonté. La syntaxe n'est pas parfaite --étiquette
ce n'est peut-être pas idéal, mais bon, ce n'est pas grave non plus! Oui, tout ceci représente un surcroît de travail et de surcharge mentale lorsque vous rédigez les spécifications, mais d'un autre côté, il vous offre une puissante possibilité de découper votre suite de tests à la demande. Sur de plus gros projets, cela peut vous faire gagner beaucoup de temps.
Ce que vous avez appris jusqu’à présent devrait vous fournir les bases absolues pour jouer avec vos propres tests: un kit de survie pour débutant. Et vraiment jouer et faire des erreurs autant que vous le pouvez. Prenez RSpec et tout le truc piloté par les tests pour essayer, et ne vous attendez pas à écrire des tests de qualité immédiatement. Il vous manque encore quelques pièces avant de vous sentir à l'aise et efficace..
Pour moi, c'était un peu frustrant au début car il était difficile de voir comment tester quelque chose alors que je ne l'avais pas encore implémenté et que je ne comprenais pas vraiment comment il se comporterait..
Les tests prouvent vraiment si vous comprenez un cadre tel que Rails et si vous savez comment les pièces s'emboîtent. Lorsque vous écrivez des tests, vous devez être capable d’écrire les attentes concernant le comportement d’un framework..
Ce n'est pas facile si vous débutez avec tout cela. Faire face à plusieurs langues spécifiques à un domaine - ici, RSpec et Rails, par exemple - plus l'apprentissage de l'API Ruby peut être déroutant. Ne vous sentez pas mal si la courbe d'apprentissage semble décourageante; cela deviendra plus facile si vous vous en tenez à cela. Faire en sorte que cette ampoule s'éteigne n'arrivera pas du jour au lendemain, mais pour moi, cela en valait vraiment la peine..