Nous avons écrit près de 3 000 tests - les données ont été codées en dur, très peu de réutilisation du code. Cette méthodologie a commencé à nous mordre dans le cul. À mesure que le système change, nous passons plus de temps à réparer les tests brisés. Nous avons des tests unitaires, d'intégration et fonctionnels.
Ce que je recherche, c'est un moyen définitif d'écrire des tests gérables et maintenables.
Cadres
.net
unit-testing
Chuck Conway
la source
la source
Réponses:
Ne les considérez pas comme des "tests unitaires brisés", car ils ne le sont pas.
Ce sont des spécifications que votre programme ne prend plus en charge.
Ne le considérez pas comme «fixant les tests», mais comme «définissant de nouvelles exigences».
Les tests doivent d'abord spécifier votre application, et non l'inverse.
Vous ne pouvez pas dire que votre implémentation fonctionne tant que vous ne savez pas que cela fonctionne. Vous ne pouvez pas dire que cela fonctionne tant que vous ne l'avez pas testé.
Quelques autres notes qui pourraient vous guider:
la source
Don't think of it as "fixing the tests", but as "defining new requirements".
Ce que vous décrivez n'est peut-être pas une si mauvaise chose, mais un indicateur de problèmes plus profonds découverts par vos tests
Si vous pouviez changer votre code et que vos tests ne se cassaient pas , ce serait suspect pour moi. La différence entre un changement légitime et un bogue n'est que le fait qu'il est demandé, ce qui est demandé est (supposé TDD) défini par vos tests.
Les données codées en dur dans les tests sont à mon humble avis une bonne chose. Les tests fonctionnent comme des falsifications, pas comme des preuves. S'il y a trop de calcul, vos tests peuvent être des tautologies. Par exemple:
Plus l'abstraction est élevée, plus vous vous rapprochez de l'algorithme, et par là, plus proche de la comparaison de l'implémentation acutale à elle-même.
La meilleure réutilisation du code dans les tests est à mon humble avis, comme dans jUnits
assertThat
, car ils gardent les tests simples. En plus de cela, si les tests peuvent être refactorisés pour partager du code, le code réel testé peut également l'être , réduisant ainsi les tests à ceux testant la base refactorisée.la source
J'ai aussi eu ce problème. Mon approche améliorée a été la suivante:
N'écrivez pas de tests unitaires à moins qu'ils ne soient le seul bon moyen de tester quelque chose.
Je suis tout à fait prêt à admettre que les tests unitaires ont le coût de diagnostic et le délai de réparation les plus bas. Cela en fait un outil précieux. Le problème est, avec l'évidence que votre kilométrage peut varier, que les tests unitaires sont souvent trop mesquins pour mériter le coût du maintien de la masse du code. J'ai écrit un exemple en bas, regardez.
Utilisez des assertions partout où elles sont équivalentes au test unitaire pour ce composant. Les assertions ont la belle propriété qu'elles sont toujours vérifiées tout au long de toute construction de débogage. Ainsi, au lieu de tester les contraintes de classe «Employé» dans une unité de test distincte, vous testez efficacement la classe Employé dans tous les cas de test du système. Les assertions ont également la propriété agréable de ne pas augmenter la masse du code autant que les tests unitaires (qui nécessitent finalement un échafaudage / moquerie / autre).
Avant que quelqu'un ne me tue: les versions de production ne doivent pas planter sur les assertions. Au lieu de cela, ils doivent se connecter au niveau "Erreur".
À titre de mise en garde pour quelqu'un qui n'y a pas encore pensé, n'affirmez rien sur l'entrée utilisateur ou réseau. C'est une énorme erreur ™.
Dans mes dernières bases de code, j'ai judicieusement supprimé les tests unitaires partout où je vois une opportunité évidente pour les assertions. Cela a considérablement réduit le coût de la maintenance dans l'ensemble et a fait de moi une personne beaucoup plus heureuse.
Préférez les tests système / d'intégration, en les implémentant pour tous vos flux principaux et expériences utilisateur. Les valises de coin n'ont probablement pas besoin d'être ici. Un test système vérifie le comportement côté utilisateur en exécutant tous les composants. Pour cette raison, un test système est nécessairement plus lent, alors écrivez ceux qui comptent (ni plus, ni moins) et vous attraperez les problèmes les plus importants. Les tests du système ont une surcharge de maintenance très faible.
Il est essentiel de se rappeler que, puisque vous utilisez des assertions, chaque test système exécutera simultanément quelques centaines de "tests unitaires". Vous êtes également plutôt assuré que les plus importants sont exécutés plusieurs fois.
Écrivez des API solides qui peuvent être testées fonctionnellement. Les tests fonctionnels sont maladroits et (avouons-le) un peu dénués de sens si votre API rend trop difficile la vérification des composants fonctionnels par eux-mêmes. Une bonne conception de l'API a) rend les étapes de test simples et b) engendre des affirmations claires et précieuses.
Le test fonctionnel est la chose la plus difficile à bien faire, surtout lorsque vous avez des composants communiquant un à plusieurs ou (pire encore, oh mon dieu) plusieurs à plusieurs à travers les barrières de processus. Plus il y a d'entrées et de sorties attachées à un seul composant, plus les tests fonctionnels sont difficiles, car vous devez isoler l'un d'entre eux pour vraiment tester sa fonctionnalité.
Sur la question de «ne pas écrire de tests unitaires», je présenterai un exemple:
L'auteur de ce test a ajouté sept lignes qui ne contribuent pas du tout à la vérification du produit final. L'utilisateur ne devrait jamais voir cela se produire, soit parce que a) personne ne devrait jamais y passer NULL (alors écrivez une assertion, alors) ou b) le cas NULL devrait provoquer un comportement différent. Si le cas est (b), écrivez un test qui vérifie réellement ce comportement.
Ma philosophie est devenue que nous ne devrions pas tester les artefacts d'implémentation. Nous ne devons tester que tout ce qui peut être considéré comme une sortie réelle. Sinon, il n'y a aucun moyen d'éviter d'écrire deux fois la masse de code de base entre les tests unitaires (qui forcent une implémentation particulière) et l'implémentation elle-même.
Il est important de noter ici qu'il existe de bons candidats pour les tests unitaires. En fait, il existe même plusieurs situations où un test unitaire est le seul moyen adéquat pour vérifier quelque chose et dans lequel il est très utile d'écrire et de maintenir ces tests. Du haut de ma tête, cette liste comprend des algorithmes non triviaux, des conteneurs de données exposés dans une API et du code hautement optimisé qui semble "compliqué" (alias "le prochain gars va probablement tout gâcher").
Mon conseil spécifique pour vous, alors: commencez à supprimer judicieusement les tests unitaires lorsqu'ils se cassent, en vous posant la question "est-ce une sortie ou est-ce que je gaspille du code?" Vous réussirez probablement à réduire le nombre de choses qui vous font perdre votre temps.
la source
Il me semble que vos tests unitaires fonctionnent comme un charme. C'est une bonne chose qu'il soit si fragile aux changements, car c'est en quelque sorte le point. De petits changements dans les tests de rupture de code afin que vous puissiez éliminer la possibilité d'erreur dans tout votre programme.
Cependant, gardez à l'esprit que vous ne devez vraiment tester que les conditions qui pourraient faire échouer votre méthode ou donner des résultats inattendus. Cela permettrait à votre unité de tester plus encline à "casser" s'il y a un problème réel plutôt que des choses insignifiantes.
Bien qu'il me semble que vous repensez fortement le programme. Dans de tels cas, faites tout ce dont vous avez besoin et supprimez les anciens tests et remplacez-les par de nouveaux par la suite. La réparation des tests unitaires ne vaut que si vous ne corrigez pas en raison de changements radicaux dans votre programme. Sinon, vous constaterez peut-être que vous consacrez trop de temps à la réécriture des tests pour être applicable dans votre nouvelle section de code de programme.
la source
Je suis sûr que d'autres auront beaucoup plus à dire, mais d'après mon expérience, ce sont des choses importantes qui vous aideront:
la source
Gérez les tests comme vous le faites avec du code source.
Contrôle de version, versions des points de contrôle, suivi des problèmes, "propriété des fonctionnalités", planification et estimation des efforts, etc.
la source
Vous devriez certainement jeter un œil aux modèles de test XUnit de Gerard Meszaros . Il a une grande section avec de nombreuses recettes pour réutiliser votre code de test et éviter la duplication.
Si vos tests sont fragiles, il se peut aussi que vous n'ayez pas assez recours pour tester les doubles. Surtout, si vous recréez des graphiques entiers d'objets au début de chaque test unitaire, les sections Arrangement de vos tests peuvent devenir surdimensionnées et vous pouvez souvent vous retrouver dans des situations où vous devez réécrire les sections Arrange dans un nombre considérable de tests simplement parce que l'une de vos classes les plus utilisées a changé. Les maquettes et les talons peuvent vous aider ici en réduisant le nombre d'objets que vous devez réhydrater pour avoir un contexte de test pertinent.
Supprimer les détails sans importance de vos configurations de test via des simulations et des talons et appliquer des modèles de test pour réutiliser le code devrait réduire considérablement leur fragilité.
la source