Dans ce dernier article sur les principes de base de RSpec, nous abordons quelques éléments douteux que vous pouvez et devriez éviter, comment composer vos tests, pourquoi vous devriez éviter autant que possible la base de données et comment accélérer votre suite de tests..
Maintenant que vous avez les bases à votre portée, nous devrions prendre le temps de discuter de quelques parties douteuses de RSpec et de TDD - quelques problèmes qui peuvent facilement être surutilisés et de certains inconvénients de l’utilisation de parties de la DSL de RSpec non réfléchies. Je veux éviter de fourrer beaucoup de concepts avancés dans vos cerveaux de TDD fraîchement éclos, mais j’estime que quelques points doivent être soulignés avant que vous ne commenciez votre première série d’essais. De plus, créer une suite de tests lente en raison de mauvaises habitudes facilement évitables est quelque chose que vous pouvez améliorer dès le début..
Bien sûr, vous devez acquérir plus d'expérience avant de pouvoir vous sentir à l'aise et efficace avec les tests, mais je parie que vous vous sentirez mieux dès le début si vous enlevez certaines des meilleures pratiques qui amélioreront votre les spécifications se multiplient sans trop étirer vos compétences pour le moment. C'est également une petite fenêtre sur des concepts plus avancés qu'il vous faudra apprendre au fil du temps pour «maîtriser» les tests. Je pense que je ne devrais pas vous déranger trop au début avec cela, car cela pourrait vous sembler compliqué et déroutant avant que vous ne développiez la vue d'ensemble qui relie tout ensemble parfaitement.
Commençons par la vitesse. Une suite rapide n'est rien qui arrive par accident; il s’agit d’un entretien «constant». Il est très important d’écouter vos tests très fréquemment - du moins si vous êtes à bord avec TDD et buvez du Kool-Aid depuis un moment - et que les suites de tests rapides rendent beaucoup plus raisonnable de faire attention aux endroits où les tests guident vous.
La vitesse d’essai est une chose à laquelle vous devez prendre grand soin. Il est essentiel de faire du test une habitude régulière et de la garder amusante. Vous souhaitez pouvoir exécuter rapidement vos tests afin d'obtenir un retour rapide pendant votre développement. Plus la suite de tests est longue, plus il est probable que vous ignoriez de plus en plus les tests, jusqu'à ce que vous ne le fassiez qu'à la fin avant de vouloir expédier une nouvelle fonctionnalité..
Cela pourrait ne pas sembler aussi grave au début, mais ce n’est pas une mince affaire. L'un des principaux avantages d'une suite de tests est qu'elle guide la conception de votre application. Pour moi, il s'agit probablement de la plus grosse victoire de TDD. Des tests plus longs rendent cette partie pratiquement impossible, car il est très probable que vous ne les exécutiez pas pour ne pas interrompre votre flux. Des tests rapides vous garantissent que vous n'avez aucune raison de ne pas exécuter vos tests.
Vous pouvez voir ce processus comme un dialogue entre vous et la suite de tests. Si cette conversation devient trop lente, c'est vraiment pénible de continuer. Lorsque votre éditeur de code offre la possibilité d'exécuter également vos tests, vous devez absolument utiliser cette fonctionnalité. Cela augmentera considérablement la vitesse et améliorera votre flux de travail. Basculer à chaque fois entre votre éditeur et un shell pour exécuter vos tests vieillit très rapidement. Mais comme ces articles sont destinés aux programmeurs débutants, je ne m'attends pas à ce que vous configuriez vos outils immédiatement. Il existe d'autres moyens d'améliorer ce processus sans avoir besoin de bricoler votre éditeur immédiatement. C'est bon à savoir, cependant, et je recommande d'intégrer de tels outils à votre flux de travail..
Sachez également que vous avez déjà appris à découper vos tests et que vous n’avez pas besoin de lancer la suite de tests complète à tout moment. Vous pouvez facilement exécuter des fichiers uniques ou même unique il
blocs-tout dans un éditeur de code capable sans jamais le laisser pour le terminal. Vous pouvez par exemple concentrer le test sur la ligne testée. Cela semble magique, pour être franc, ça ne devient jamais ennuyeux.
Écrire trop dans la base de données, souvent très inutilement, est un moyen sûr de ralentir rapidement votre suite de tests. Dans de nombreux scénarios de test, vous pouvez simuler les données dont vous avez besoin pour configurer un test et vous concentrer uniquement sur les données directement sous test. La plupart du temps, vous n’avez pas besoin de consulter la base de données, surtout pas pour les parties qui ne sont pas testées directement et qui ne prennent en charge que le test: un utilisateur connecté pendant que vous testez le montant à payer à un moment donné. la caisse, par exemple. L'utilisateur est comme un extra qui peut être simulé.
Vous devriez essayer de ne pas toucher la base de données autant que possible, car cela ralentit considérablement la suite de tests. Essayez également de ne pas configurer trop de données si vous n'en avez pas du tout besoin. Cela peut être très facile à oublier avec les tests d'intégration en particulier. Les tests unitaires sont souvent beaucoup plus ciblés par définition. Cette stratégie s’avérera très efficace pour éviter de ralentir les suites de tests au fil du temps. Choisissez vos dépendances avec le plus grand soin et voyez quelle est la plus petite quantité de données permettant à vos tests de réussir.
Je ne veux pas entrer dans les détails pour le moment - il est probablement un peu trop tôt dans votre trajectoire pour parler de talons, d'espions, de faux et d'autres choses. Vous confondre ici avec des concepts aussi avancés semble contre-productif et vous les rencontrerez assez tôt. Il existe de nombreuses stratégies pour des tests rapides qui impliquent également d'autres outils que RSpec. Pour le moment, essayez de vous faire une idée plus générale de RSpec et des tests en général.
Vous voulez également essayer de tout tester une fois, si possible. Ne re-testez pas la même chose encore et encore, c'est du gaspillage. Cela se produit principalement par accident et / ou par de mauvaises décisions de conception. Si vous avez commencé à avoir des tests lents, c’est un endroit facile à refactoriser pour obtenir un boost de vitesse..
La majorité de vos tests doivent également être au niveau de l'unité, testant vos modèles. Cela permettra non seulement de garder les choses rapides, mais vous fournira également le meilleur rendement pour votre argent. Les tests d'intégration qui testent des flux de production entiers, imitant dans une certaine mesure le comportement de l'utilisateur en rassemblant plusieurs composants et les testant de manière synchrone, devraient constituer la plus petite partie de votre pyramide de test. Celles-ci sont plutôt lentes et «chères». Peut-être que 10% de vos tests globaux n’est pas irréaliste, mais cela dépend, je suppose.
Il peut être difficile d’exercer le moins possible la base de données, car il est nécessaire d’apprendre un peu plus d’outils et de techniques pour y parvenir efficacement. Il est toutefois essentiel de créer des suites de tests suffisamment rapides pour pouvoir exécuter vos tests fréquemment..
Le serveur Spring est une fonctionnalité de Rails et précharge votre application. Ceci est une autre stratégie simple pour augmenter votre vitesse de test de manière significative - je devrais ajouter, dès la sortie de la boîte. Cela permet simplement à votre application de fonctionner en arrière-plan sans avoir à la démarrer à chaque exécution de test. Il en va de même pour les tâches Rake et les migrations. ceux-ci iront aussi vite.
Depuis Rails 4.1, Spring a été automatiquement inclus dans Rails - ajouté au Gemfile - et vous n'avez pas besoin de faire beaucoup pour démarrer ou arrêter ce préchargeur. Dans le passé, nous devions utiliser nos propres outils de choix, ce que vous pouvez toujours faire si vous avez d’autres préférences. Ce qui est vraiment sympa et attentionné, c’est qu’il va redémarrer automatiquement si vous modifiez des gemmes, des initialiseurs ou des fichiers de configuration, un toucher agréable et pratique car il est facile d’oublier de prendre soin de vous-même.
Par défaut, il est configuré pour exécuter des rails
et râteau
commandes seulement. Nous devons donc le configurer pour fonctionner également avec le rspec
commande pour l'exécution de nos tests. Vous pouvez demander le statut de printemps comme suit:
statut de printemps
Le printemps n'est pas en cours.
La sortie nous indiquant que Spring n’est pas en cours, vous devez simplement le démarrer avec serveur de printemps.
Quand tu cours maintenant statut de printemps
, vous devriez voir quelque chose de semblable à ceci:
Le printemps est en cours d'exécution: serveur de printemps 3738 | rspec-dummy | a commencé il y a 21 secondes
Maintenant, nous devrions vérifier ce que le printemps est configuré pour précharger.
printemps binstub --all
* bin / rake: printemps déjà présent * bin / rails: printemps déjà présent
Cela nous indique que Spring précharge les rails pour râteau
et des rails
commandes, et rien d'autre à ce jour. Que nous devons prendre en charge. Nous devons ajouter le joyau commandes de printemps-rspec
, et nos tests sont alors prêts à être préchargés.
gem 'spring-orders-rspec', groupe:: développement
bundle installer bundle exec spring binstub rspec
Je vous épargne la sortie de installation groupée
; Je suis sûr que vous en avez déjà vu plus que votre part. Fonctionnement bundle exec spring binstub rspec
, d'autre part, génère un bin / rspec
fichier qui l'a ajouté à être préchargé par Spring. Voyons si cela a fonctionné:
printemps binstub --all
Cela a créé quelque chose appelé binstub-un wrapper pour les exécutables tels que des rails
, râteau, paquet
, rspec
et tel que, lorsque vous utilisez le rspec
commande, il utilisera Spring. En plus, ces binstubs garantissent que vous exécutez ces exécutables dans le bon environnement. Ils vous permettent également d'exécuter ces commandes à partir de tous les répertoires de votre application, pas seulement à partir de la racine. L’autre avantage des binstubs est que vous n’avez pas à ajouter des bundle exec
avec tout.
* bin / rake: printemps déjà présent * bin / rspec: printemps déjà présent * bin / rails: printemps déjà présent
Ça a l'air OK! Arrêtons et redémarrons le serveur Spring avant de poursuivre:
serveur d'arrêt de ressort
Alors maintenant, vous exécutez le serveur Spring dans une fenêtre de terminal dédiée et vous exécutez vos tests avec une syntaxe légèrement différente dans une autre. Nous avons simplement besoin de préfixer chaque test avec le printemps
commander:
printemps rspec spec
Cela exécute tous vos fichiers de spécification, bien sûr. Mais il n'y a pas besoin de s'arrêter là. Vous pouvez également exécuter des fichiers uniques ou des tests étiquetés via Spring-no problem! Et ils vont tous être rapides comme l'éclair maintenant; sur de plus petites suites de tests, elles semblent vraiment presque instantanées. En plus de cela, vous pouvez utiliser la même syntaxe pour votre des rails
et râteau
commandes. Gentil, hein?
râteau à ressort rails de ressort g modèle BondGirl nom: string râteau à ressort db: migrer…
Nous avons donc sorti Spring de la boîte pour accélérer les choses dans Rails, mais nous ne devons pas oublier d’ajouter ce petit bijou pour que Spring sache jouer au ballon avec RSpec..
Les choses mentionnées dans cette section sont probablement bonnes à éviter tant que vous pouvez trouver une autre solution pour elles. L'usage excessif de certaines des commodités de RSpec peut conduire à développer de mauvaises habitudes de test, à tout le moins douteuses. Ce que nous allons discuter ici est commode en surface mais pourrait vous mordre un peu plus tard..
Ils ne doivent pas être considérés comme des anti-motifs - des choses à éviter tout de suite - mais plutôt considérés comme des «odeurs», des choses sur lesquelles vous devez faire attention et qui peuvent entraîner un coût important que vous ne voulez souvent pas payer. Le raisonnement de cette opération implique quelques idées et concepts supplémentaires que vous ne connaissez probablement pas encore en tant que débutant. Et franchement, il se peut que vous soyez un peu dépassé, mais je devrais au moins vous renvoyer à la maison avec quelques-uns. drapeaux rouges à réfléchir et à mémoriser pour l'instant.
laisser
Avoir beaucoup de laisser
Les références peuvent sembler très pratiques au début, surtout parce qu'elles assèchent un peu les choses. Cela semble être une extraction assez bonne au début de les avoir en haut de vos fichiers, par exemple. D'un autre côté, ils peuvent facilement vous empêcher de comprendre votre propre code si vous visitez des tests spécifiques plus ou moins longtemps. Ne pas avoir les données configurées dans votre laisser
les blocs ne facilite pas trop la compréhension de vos tests. Ce n’est pas aussi anodin que cela puisse paraître au premier abord, surtout si d’autres développeurs sont impliqués et doivent également lire votre travail..
Plus le nombre de développeurs impliqués est important, plus cette confusion devient coûteuse. Ce n'est pas seulement une perte de temps si vous devez traquer laisser
références encore et encore, il est également stupide car il aurait été évitable avec très peu d’effort. La clarté est roi, cela ne fait aucun doute. Un autre argument pour garder ces données en ligne est que votre suite de tests sera moins fragile. Vous ne voulez pas construire un château de cartes qui devient de plus en plus instable à chaque laisser
c'est cacher des détails de chaque test. Vous avez probablement appris que l'utilisation de variables globales n'est pas une bonne idée. Dans ce sens, laisser
est semi-global dans vos fichiers de spécifications.
Un autre problème est que vous devrez tester de nombreuses variantes, différents états pour des scénarios similaires. Vous serez bientôt à court de nom raisonnable laisser
instructions pour couvrir toutes les versions dont vous pourriez avoir besoin - ou vous retrouver avec une botte de foin de tonnes de variations d'état portant le même nom. Lorsque vous configurez les données directement dans chaque test, vous ne rencontrez pas ce problème. Les variables locales sont peu onéreuses, très lisibles et ne dérangent pas les autres portées. En fait, ils peuvent être encore plus expressifs, car vous n'avez pas besoin de penser à des tonnes d'autres tests qui pourraient poser problème avec un nom particulier. Vous voulez éviter de créer un autre DSL en plus du framework que tout le monde doit déchiffrer pour chaque test utilisant laisser
. J'espère que cela ressemble beaucoup à une perte de temps pour tout le monde.
avant
Et après
Enregistrer des choses comme avant
, après
et ses variations pour des occasions spéciales et ne l'utilisez pas tout le temps, partout. Voyez-le comme l'un des gros canons que vous retirez pour des méta-trucs. Nettoyer vos données est un bon exemple trop méta pour que chaque test puisse être traité. Vous voulez extraire cela, bien sûr.
Souvent, vous mettez le laisser
en haut du fichier et cachez ces détails des autres tests qui les utilisent en descendant le fichier. Vous souhaitez que les informations et les données pertinentes soient aussi proches que possible de la partie où vous exercez le test, et non des kilomètres de distance, ce qui rend plus obscurs les tests individuels..
À la fin, on a l'impression qu'il y a trop de corde pour se pendre, parce que laisser
introduit des appareils largement partagés. Cela se résume essentiellement à des données de test factices dont la portée n'est pas assez étroite.
Cela conduit facilement à une odeur majeure appelée «invité mystère». Cela signifie que vous avez des données de test qui apparaissent de nulle part ou qui sont simplement supposées. Vous aurez souvent besoin de les rechercher d'abord pour comprendre un test, en particulier si un certain temps s'est écoulé depuis l'écriture du code ou si vous êtes nouveau dans une base de code. Il est beaucoup plus efficace de définir vos données de test en ligne exactement là où vous en avez besoin - lors de la configuration d'un test particulier et non dans un cadre beaucoup plus large..
… Décrire Agent, '#print_favorite_gadget' do it 'affiche le nom, le rang et le gadget favori de l'agent' attend (agent.print_favorite_gadget) .to eq ('Commander Bond a un faible pour les Aston Martins')
Quand on regarde ça, ça se lit assez bien, non? C'est succinct, une ligne, assez propre, non? Ne nous leurrons pas. Ce test ne nous en dit pas beaucoup sur la agent
en question, et cela ne nous dit pas toute l'histoire. Les détails de la mise en œuvre sont importants, mais nous ne les voyons pas. L'agent semble avoir été créé ailleurs et il faudrait d'abord le rechercher pour comprendre parfaitement ce qui se passe ici. Donc, il semble peut-être élégant à la surface, mais il vient avec un prix lourd.
Oui, vos tests ne seront peut-être pas toujours très secs à cet égard, mais je pense que c'est un petit prix à payer pour être plus expressif et plus facile à comprendre. Il existe certes des exceptions, mais elles devraient être appliquées simplement à des circonstances exceptionnelles après avoir épuisé toutes les options offertes par Ruby..
Avec un invité mystère, vous devez savoir d’où viennent les données, pourquoi elles sont importantes et quelles sont leurs spécificités. Ne pas voir les détails de la mise en œuvre dans un test particulier lui-même ne fait que rendre votre vie plus dure que nécessaire. Je veux dire, fais ce que tu ressens si tu travailles sur tes propres projets, mais quand d'autres développeurs sont impliqués, il serait plus agréable de penser à rendre leur expérience avec ton code aussi fluide que possible..
Comme pour beaucoup de choses, bien sûr, l'essentiel réside dans les détails, et vous ne voulez pas garder vous-même et les autres dans le noir à ce sujet. La lisibilité, la concision et la commodité de laisser
ne devrait pas entraîner une perte de clarté sur les détails de la mise en œuvre et les erreurs d’orientation. Vous voulez que chaque test individuel raconte toute l'histoire et fournisse tout le contexte pour le comprendre immédiatement.
En résumé, vous voulez avoir des tests faciles à lire et à raisonner à chaque test. Essayez de spécifier tout ce dont vous avez besoin dans le test, et pas plus que cela. Ce type de déchets commence à «sentir» comme n'importe quel autre type de bric-à-brac. Cela implique également que vous deviez ajouter les détails dont vous avez besoin pour des tests spécifiques le plus tard possible - lorsque vous créez des données de test globales, dans le scénario réel et non dans un lieu distant. L'utilisation suggérée de laisser
offre un autre genre de commodité qui semble s'opposer à cette idée.
Reprenons l'exemple précédent et implémentons-le sans le problème de l'invité mystère. Dans la solution ci-dessous, nous trouverons toutes les informations pertinentes pour le test en ligne. Nous pouvons rester fidèles à cette spécification si elle échoue et si elle n'a pas besoin de rechercher des informations supplémentaires..
… Décrire Agent, '#print_favorite_gadget' do it 'affiche le nom, le rang et le gadget préféré de l'agent' do agent = Agent.new (nom: 'James Bond', rang: 'Commander', favori_gadget: 'Aston Martin') (agent.print_favorite_gadget) .to eq ('Le commandant Bond a un faible pour les Aston Martins') end end
Ce serait bien si laisser
vous permettent de configurer des données de test barebone que vous pouvez améliorer en fonction du besoin de savoir dans chaque test spécifique, mais ce n'est pas ainsi que laisser
est roulant. C'est ainsi que nous utilisons les usines via Factory Girl ces jours-ci.
Je vous épargne les détails, d’autant plus que j’ai déjà écrit quelques articles à ce sujet. Voici mes articles sur mesure pour débutants 101 et 201 sur ce que Factory Girl a à offrir - si vous êtes déjà curieux à ce sujet. Il est écrit pour les développeurs sans beaucoup d'expérience.
Voyons un autre exemple simple qui exploite bien les données de test de support configurées en ligne:
décris Agent, '#current_mission' do it 'affiche le statut actuel de la mission de l'agent et son objectif' do mission_octopussy = Mission.nouveau (nom: 'Octopussy', objectif: 'Arrêtez le mauvais type blanc') bond = Agent.nouveau (nom : 'James Bond', statut: 'Opération sous couverture', section: '00', licence_to_kill: true) bond.missions << mission_octopussy expect(bond.current_mission).to eq ('Agent Bond is currently engaged in an undercover operation for mission Octopussy which aims to stop bad white dude') end end
Comme vous pouvez le constater, nous avons toutes les informations dont ce test a besoin à un endroit et il n’est pas nécessaire de rechercher des informations spécifiques à un autre endroit. Il raconte une histoire et n'est pas obscur. Comme mentionné, ce n'est pas la meilleure stratégie pour le code DRY. La récompense est bonne, cependant. La clarté et la lisibilité l'emportent sur ce peu de code répétitif, en particulier dans les bases de code volumineuses.
Par exemple, supposons que vous écriviez une nouvelle fonctionnalité, apparemment sans rapport, et que soudainement ce test commence à échouer en tant que dommage collatéral et que vous n’ayez plus touché à ce fichier de spécification depuis des lustres.
Pensez-vous que vous serez satisfait si vous devez d'abord déchiffrer les composants d'installation afin de comprendre et de corriger ce test qui échoue avant de pouvoir continuer avec une fonctionnalité complètement différente sur laquelle vous travaillez? Je crois que non! Vous voulez sortir de cette spécification "sans rapport" le plus tôt possible et revenir à la finition de l'autre fonctionnalité.
Lorsque vous trouvez toutes les données de test à un endroit où vos tests vous indiquent où il échoue, vous augmentez vos chances de le réparer rapidement sans «télécharger» une partie complètement différente de l'application dans votre cerveau..
Vous pouvez nettoyer et DRY votre code de manière significative en écrivant vos propres méthodes d'assistance. RSpec DSL n'est pas nécessaire pour une méthode aussi économique qu'une méthode Ruby..
Supposons que vous ayez trouvé quelques appareils répétitifs qui commencent à se sentir un peu sales. Au lieu d'aller avec un laisser
ou un assujettir
, définir une méthode au bas d'un bloc decrire - une convention - et en extraire les points communs. S'il est utilisé un peu plus largement dans un fichier, vous pouvez également le placer au bas du fichier..
Un effet secondaire intéressant est que vous ne traitez pas de variables semi-globales de cette façon. Vous éviterez également de faire un tas de modifications partout si vous devez modifier un peu les données. Vous pouvez maintenant accéder à un endroit central où la méthode est définie et affecter tous les endroits où elle est utilisée à la fois. Pas mal!
décrire Agent, '#current_status' do it 'spécule sur le choix de la destination de l'agent si le statut est vacant' do bond = Agent.new (nom: 'James Bond', statut: 'En vacance', section: '00', licence_to_kill : true) expect (bond.current_status) .to eq ('Le commandant Bond est en vacances, probablement aux Bahamas') et finit par 'spéculer sur le choix de la destination du quartier-maître si le statut est en vacances' do q = Agent.new (nom: 'Q', statut: 'En vacances', section: '00', licence_to_kill: true) attend (q.current_status) .to eq ('Le quartier-maître est en vacances, probablement à DEFON CON') end end
Comme vous pouvez le constater, il existe un peu de code de configuration répétitif, et nous voulons éviter de l'écrire encore et encore. Au lieu de cela, nous voulons seulement voir l'essentiel pour ce test et qu'une méthode construise le reste de l'objet pour nous..
décrire Agent, '#current_status' do it 'spécule sur le choix de l'agent de destination si le statut est vacances' do bond = build_agent_on_vacation ('James Bond', 'En vacances') attend (bond.current_status) .to eq ('Commander Bond est en vacances, probablement aux Bahamas ') et il spécule sur le choix de la destination par le quartier-maître si le statut est vacances' do q = build_agent_on_vacation ('Q', 'En vacances') attend (q.current_status) .to eq (' Le quartier-maître est en vacances, probablement à DEF CON ') end def build_agent_on_vacation (nom, statut) Agent.new (nom: nom, statut: statut, section:' 00 ', licence_to_kill: true) end end
Maintenant, notre méthode extraite prend en charge la section
et permis de tuer
ne nous détourne pas de l'essentiel du test. Bien sûr, il s’agit d’un exemple factice, mais vous pouvez adapter sa complexité autant que vous le souhaitez. La stratégie ne change pas. C'est une technique de refactorisation très simple - c'est pourquoi je l'introduis si tôt - mais l'une des plus efficaces. De plus, éviter les outils d’extraction proposés par RSpecs est une évidence..
Vous devez également faire attention à la façon dont ces méthodes d'assistance peuvent être expressives sans payer de supplément.
Éviter quelques parties de RSpec DSL et utiliser à bon escient les principes de la bonne vieille Ruby et de la programmation orientée objet est un bon moyen d'aborder la rédaction de vos tests. Vous pouvez utiliser librement l'essentiel, décrire
, le contexte
et il
, bien sûr.
Trouvez une bonne raison d'utiliser d'autres parties de RSpec et évitez-les aussi longtemps que vous le pouvez. Tout simplement parce que les choses peuvent sembler pratiques et fantaisistes n'est pas une raison suffisante pour les utiliser, il est préférable de garder les choses plus simples.
Simple c'est bien; il garde vos tests en bonne santé et rapide.