43 votes

Test: comment se concentrer sur le comportement plutôt que sur la mise en œuvre sans perdre de vitesse?

Il semble qu'il y a deux approches totalement différentes à des tests, et je voudrais citer deux d'entre eux.

Le truc, c'est que ces opinions ont été exprimées il y a 5 ans (2007), et je suis intéressé, ce qui a changé depuis lors, et qui dois-je aller.

Brandon Gardiens:

La théorie est que les tests sont censés être agnostique de la la mise en œuvre. Ce qui conduit à moins cassants tests et fait des tests le résultat (ou le comportement).

Avec RSpec, j'ai envie de l'approche commune de complètement se moquant de votre des modèles pour tester vos contrôleurs finit de vous forcer à regarder de trop dans la mise en œuvre de votre contrôleur.

Cela, en soi, n'est pas trop mauvais, mais le problème est qu'il est trop pairs dans le contrôleur de dicter la façon dont le modèle est utilisé. Pourquoi est-il importe si mon contrôleur des appels Chose.de nouveau? Que faire si mon contrôleur décide prendre la Chose.créer! et de sauvetage de la route? Que faire si mon modèle est doté d'une spécial de l'initialiseur de la méthode, comme Chose.build_with_foo? Mon spec pour le comportement ne doit pas échouer si je change la mise en œuvre.

Ce problème est encore pire quand vous avez imbriqué des ressources et sont la création de plusieurs modèles par le contrôleur. Certains de mes méthodes de configuration de la fin jusqu'à 15 ou plus de lignes de long et TRÈS fragile.

RSpec a l'intention d'isoler complètement de votre contrôleur logique à partir de vos modèles, qui sonne bien en théorie, mais presque va à l'encontre de la grain pour une pile intégrée comme des Rails. Surtout si vous pratiquez le maigre contrôleur/modèle de graisse de la discipline, le montant de la logique du contrôleur devient très faible, et la configuration devient énorme.

Qu'est donc un BDD-wannabe faire? Un pas en arrière, le comportement que j' bien envie de la tester n'est pas que mon contrôleur des appels Chose.de nouveau, mais que, compte tenu des paramètres X, il crée une chose nouvelle et redirige vers elle.

David Chelimsky:

Il est tout au sujet de faire des compromis.

Le fait que l'AR choisit l'héritage plutôt que de la délégation, nous met en un test de bind – nous devons être couplé à la base de données OU de nous devons être plus intime avec la mise en œuvre. Nous acceptons ce choix de conception parce que nous profiter de tous les bienfaits de l'expressivité et SÈCHE-ness.

Dans aux prises avec le dilemme, j'ai choisi des tests plus rapides au prix de un peu plus fragile. Vous avez le choix de moins cassants tests au coût de courir un peu plus lent. C'est un compromis de toute façon.

Dans la pratique, j'exécute les tests des centaines, si pas des milliers de fois, jour (j'utilise de l'autotest et de prendre très granulaire étapes) et si je change d' Utilisation de la "nouvelle" ou "créer" presque jamais. Aussi en raison de granulés étapes, de nouveaux les modèles qui apparaissent sont très volatils au premier abord. Le valid_thing_attrs approche réduit la douleur de cette un peu, mais cela signifie toujours que chaque nouveau champ obligatoire signifie que je dois changer valid_thing_attrs.

Mais si votre approche est de travailler pour vous dans la pratique, puis sa bonne! Dans fait, je voudrais vous recommandons fortement de publier un plugin avec des générateurs qui produisent de la exemples de la façon dont vous les aimez. Je suis sûr que beaucoup de personnes bénéficieront de cette.

Ryan Bates:

Par curiosité, combien de fois utilisez-vous des objets fantaisie dans vos tests/specs? Peut-être que je suis en train de faire quelque chose de mal, mais je trouve sévèrement limiter. Depuis le passage à rSpec plus d'un mois, j'ai fait ce qu'ils recommandent dans les docs où le contrôleur et la vue de couches ne frappez pas la base de données et les modèles sont complètement moqué out. Cela vous donne un bon boost de vitesse et rend certaines choses plus facile, mais je trouve les inconvénients de le faire l'emportent de loin sur les avantages. Depuis en utilisant des objets fantaisie, mes specs ont transformé en un entretien cauchemar. Spécifications sont destinés à tester le comportement, pas la mise en œuvre. Je n'ai pas de soins si une méthode a été appelée, je veux juste m'assurer que le résultat obtenu est correcte. Parce que les moqueries fait spécifications pointilleux sur la la mise en œuvre, il rend simple refactorings (qui ne changent pas la le comportement) impossible de le faire sans avoir à constamment revenir en arrière et "fixer" les spécifications. Je suis beaucoup d'opinions sur ce qu'est une spec/essais doivent le couvercle. Un test ne devrait pause lors de l'application des pauses. C'est l'un la raison pourquoi je n'ai guère de test de la couche de la vue, car je le trouve trop rigide. Cela conduit souvent à des tests de rupture sans l'application de rupture lors de l' en changeant peu de choses dans la vue. Je suis la recherche le même problème avec des simulacres. En plus de tout cela, je viens de réaliser aujourd'hui que se moquant/stubbing une méthode de classe (parfois) autour de bâtons entre les spécifications. Spécifications devraient être autonome et n'est pas influencée par d'autres spécifications. Ce que les pauses la règle et conduit à la délicate bugs. Qu'ai-je appris de tout cela? Être attention lorsque vous utilisez les moqueries. Stubbing n'est pas aussi mauvais, mais il a encore certaines des mêmes questions.

J'ai pris depuis quelques heures et enlevé presque tous se moque de mes spécifications. J'ai aussi intégré le contrôleur et la vue des specs à une utilisation "integrate_views" dans le contrôleur spec. Je suis également le chargement de tous les luminaires pour chaque contrôleur de spec donc, il y a des données de test pour remplir les points de vue. Le résultat final? Mes specs sont plus court, plus simple, plus cohérente, moins rigide, et ils permettent de tester l'ensemble de la pile (modèle, vue, contrôleur), donc pas de bugs, que vous pouvez glisser à travers les mailles du filet. Je suis pas dire que c'est la "bonne" façon pour tout le monde. Si votre projet nécessite un très stricte spec cas, alors c'est peut-être pas pour vous, mais dans mon cas c'est les mondes mieux que ce que j'avais avant d'utiliser des simulacres. J'ai encore pensez stubbing est une bonne solution en quelques endroits, donc je suis encore en train de faire qu'.

16voto

bkeepers Points 373

Je pense que tous les trois opinions sont encore tout à fait valable. Ryan et moi avons été aux prises avec la maintenabilité de se moquer, tandis que David a senti le maintien compromis en valait la peine, pour l'augmentation de la vitesse.

Mais ces inconvénients sont les symptômes d'un problème plus profond, qui David fait allusion en 2007: ActiveRecord. La conception de ActiveRecord vous encourage à créer dieu objets que de trop en faire, trop en savoir sur le reste du système, et qui ont trop de surface. Cela conduit à des tests qui ont trop de choses à tester, en savoir trop sur le reste du système, et sont soit trop lent, ou cassants.

Alors quelle est la solution? Séparer autant de votre application à partir de la cadre que possible. Écrire beaucoup de petites classes de modèle de votre domaine et de ne pas hériter de quoi que ce soit. Chaque objet doit avoir une superficie limitée (pas plus de quelques méthodes) et des dépendances explicites transmis dans le constructeur.

Avec cette approche, j'ai seulement été écrit deux types de tests: isolé des tests unitaires, et full-stack système de tests. Dans les tests d'isolement, j'ai simulé ou stub tout ce qui n'est pas l'objet sous test. Ces tests sont incroyablement rapides et souvent ne nécessitent même pas de chargement de l'ensemble de l'environnement Rails. La pile complète de tests de l'exercice de l'ensemble du système. Ils sont extrêmement lents et donner des commentaires inutiles quand ils échouent. J'écris aussi peu que nécessaire, mais assez pour me donner de la confiance que tous mes bien-testé objets s'intègrent bien.

Malheureusement, je ne peux pas vous montrer un exemple de projet qui fait bien cela (encore). Je parle un peu de ça dans ma présentation sur le Pourquoi de Notre Code d'Odeurs, de regarder Corey Haines présentation Rapide des Rails de Tests, et je recommande fortement la lecture de Croissance Orientée Objet Logiciel Guidé par les Tests.

9voto

ryanb Points 11043

Merci pour l'établissement des devis à partir de 2007. Il est amusant de regarder en arrière.

Mon approche de test est couvert dans ce RailsCasts épisode dont j'ai été très heureux avec. En résumé, j'ai deux niveaux de tests.

  • Haut niveau: je demande d'utilisation de spécifications dans RSpec, Capybara, et d'un MAGNÉTOSCOPE. Les Tests peuvent être marqués à exécuter JavaScript nécessaire. Se moquant évite ici parce que le but est de tester l'ensemble de la pile. Chaque contrôleur de l'action est testé au moins une fois, peut-être un peu de temps.

  • Niveau bas: C'est là toute la logique complexe est testé - principalement des modèles et des aides. - Je éviter les moqueries ici. Les tests de frapper la base de données ou les objets qui l'entourent, si nécessaire.

Avis il n'y a pas de contrôleur ou de la vue des specs. Je pense qu'elles sont couvertes de manière adéquate dans la demande de spécifications.

Puisqu'il y a peu moqueur, comment puis-je garder les tests rapides? Voici quelques conseils.

  • Éviter les excès de la logique dans le haut des tests de niveau. Toute logique complexe doit être déplacé vers le niveau inférieur.

  • Lors de la génération de documents (comme avec Factory Girl), utilisez build premier et seul commutateur create lorsque nécessaire.

  • L'utilisation de la Garde avec Spork sauter les Rails temps de démarrage. Les tests en question sont souvent effectuée dans un délai de quelques secondes après l'enregistrement du fichier. Utiliser un :focus balise dans RSpec pour limiter le nombre de tests à exécuter lorsque vous travaillez sur une zone spécifique. Si c'est une grande suite de tests, définissez all_after_pass: false, all_on_start: false dans le Guardfile à seulement courir tous en cas de besoin.

  • J'utilise plusieurs affirmations par test. Exécuter le même code d'installation pour chaque assertion permettra d'accroître considérablement le temps de test. RSpec imprimer la ligne qui a échoué de sorte qu'il est facile de le localiser.

Je trouve se moquant ajoute la fragilité pour les tests c'est pourquoi j'ai l'éviter. Vrai, il peut être grande comme une aide pour OO design, mais dans la structure d'une application Rails, ce n'est pas aussi efficace. Au lieu de cela, je m'appuie fortement sur le refactoring et de laisser le code lui-même me dire comment la conception devrait aller.

Cette approche fonctionne mieux sur les petites et moyennes taille des applications Rails sans vaste, complexe domaine de la logique.

8voto

Myron Marston Points 8940

Grandes questions et de grand débat. @ryanb et @bkeepers mentionner qu'ils écrivent seulement deux types de tests. Je prends une approche similaire, mais ont un troisième type de test:

  • Unité de tests: des tests indépendants, généralement, mais pas toujours, à l'encontre de la plaine objets ruby. Mes tests unitaires n'impliquent pas de la DB, 3e partie des appels d'API, ou tout autre choses.
  • Les tests d'intégration: ce sont toujours l'accent sur les tests d'une classe; la différence est qu'ils intègrent la classe avec l'externe éviter les trucs que j'ai dans mes tests unitaires. Mes modèles ont souvent à la fois des tests unitaires et des tests d'intégration, où les tests unitaires se concentrer dans la logique pure, qui peut être testé w/o impliquant la DB, et les tests d'intégration impliquera la DB. En plus, j'ai tendance à tester la 3ème partie de l'API wrappers avec les tests d'intégration, à l'aide d'un MAGNÉTOSCOPE pour garder les tests rapide et déterministe, mais en laissant mon CI s'appuie rendre les requêtes HTTP pour de vrai (pour attraper les changements de l'API).
  • Des tests d'acceptation: de bout en bout, les tests, pour une fonction entière. Ce n'est pas seulement à propos de l'INTERFACE utilisateur de tester via le capybara; je fais la même chose dans mes joyaux, ce qui ne peut pas avoir un HTML de l'INTERFACE utilisateur à tous. Dans ces cas, cette exercices quel que soit le bijou ne de bout en bout. J'ai aussi tendance à utiliser le MAGNÉTOSCOPE dans ces tests (si ils font externe requêtes HTTP), et comme dans mes tests d'intégration, mon implant construire est configuré pour effectuer les requêtes HTTP pour de vrai.

Aussi loin que se moquant va, je n'ai pas de "one size fits all" approche. J'ai vraiment overmocked dans le passé, mais je trouve que c'est une technique très utile, en particulier lors de l'utilisation de quelque chose comme rspec-le-feu. En général, j'ai simulé des collaborateurs en jouant des rôles librement (surtout si je les possède, et qu'ils sont des objets de service) et essayer de l'éviter dans la plupart des autres cas.

Probablement le plus grand changement pour mes tests au cours de la dernière année a été inspiré par DAS: alors que j'ai l'habitude d'avoir un spec_helper.rb qui charge l'intégralité de l'environnement, maintenant je charger explicitement juste la classe sous test (et toutes ses dépendances). En plus de l'amélioration de la vitesse d'essai (qui fait une énorme différence!) il m'aide à identifier lors de ma classe-sous-test est de tirer dans de trop nombreuses dépendances.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X