Quelle est la profondeur de vos tests unitaires?

Ce que j’ai découvert à propos de TDD, c’est qu’il faut du temps pour mettre en place vos tests et être naturellement paresseux. Je veux toujours écrire le moins de code possible. La première chose que je semble faire est de tester mon constructeur a défini toutes les propriétés, mais est-ce excessif?

Ma question est de savoir à quel niveau de granularité écrivez-vous les tests unitaires?

..et y a-t-il un cas de test excessif?

    Je suis payé pour du code qui fonctionne, pas pour des tests, donc ma philosophie est de tester le moins possible pour atteindre un niveau de confiance donné (je pense que ce niveau de confiance est élevé par rapport aux standards du secteur, mais ça pourrait être de l’orgueil) . Si je ne fais pas typiquement une sorte d’erreur (comme définir les mauvaises variables dans un constructeur), je ne le teste pas. J’ai tendance à donner un sens aux erreurs de test, alors je fais très attention quand j’ai une logique avec des conditions compliquées. Lors du codage sur une équipe, je modifie ma stratégie pour tester soigneusement le code que nous avons, collectivement, tendance à nous tromper.

    Différentes personnes auront des stratégies de test différentes basées sur cette philosophie, mais cela me semble raisonnable compte tenu de l’état immature de la compréhension de la manière dont les tests peuvent le mieux s’intégrer dans la boucle interne du codage. Dans dix ou vingt ans, nous aurons probablement une théorie plus universelle des tests à écrire, des tests à ne pas écrire et de la différence. En attendant, l’expérimentation semble en ordre.

    Ecrivez des tests unitaires pour les éléments que vous prévoyez de casser et pour les cas extrêmes. Après cela, les cas de test doivent être ajoutés au fur et à mesure que les rapports de bogues arrivent – avant d’écrire le correctif du bogue. Le développeur peut alors être sûr que:

    1. Le bug est corrigé
    2. Le bug ne réapparaîtra pas.

    D’après le commentaire ci-joint, je pense que cette approche de l’écriture des tests unitaires pourrait poser problème si de nombreux bogues sont découverts au fil du temps dans une classe donnée. C’est probablement là que la discrétion est utile – en ajoutant des tests unitaires uniquement pour les bogues susceptibles de se reproduire, ou lorsque leur réapparition causerait de sérieux problèmes. J’ai trouvé qu’une mesure des tests d’intégration dans les tests unitaires peut être utile dans ces scénarios – les chemins de code de test de code supérieur peuvent couvrir les chemins de code plus bas.

    Tout devrait être aussi simple que possible, mais pas plus simple. – A. Einstein

    L’une des choses les plus mal comsockets à propos de TDD est le premier mot. Tester. C’est pourquoi BDD est arrivé. Parce que les gens ne comprenaient pas vraiment que le premier D était le plus important, à savoir Driven. Nous avons tous tendance à réfléchir un peu aux essais et à parler un peu de la conception. Et je suppose que ceci est une réponse vague à votre question, mais vous devriez probablement réfléchir à la manière de conduire votre code, au lieu de ce que vous testez réellement; C’est quelque chose qu’un outil de couverture peut vous aider. Le design est un problème plus important et plus problématique.

    Pour ceux qui proposent de tester “Everything”: se rendre compte que “tester complètement” une méthode comme int square(int x) nécessite environ 4 milliards de tests dans des langages communs et des environnements typiques.

    En fait, c’est encore pire que cela: une méthode void setX(int newX) est également obligée de ne pas modifier les valeurs des autres membres à part x – testez-vous que obj.y , obj.z , etc. restnt tous inchangés après avoir appelé obj.setX(42); ?

    Il est seulement pratique de tester un sous-ensemble de “tout”. Une fois que vous l’acceptez, il devient plus acceptable de ne pas tester des comportements incroyablement basiques. Chaque programmeur a une dissortingbution de probabilité des emplacements de bogue; L’approche intelligente consiste à concentrer votre énergie sur les régions de test où vous estimez que la probabilité de bogue est élevée.

    La réponse classique est “testez tout ce qui pourrait éventuellement casser”. J’interprète cela comme signifiant que les testeurs et les getters qui ne font rien sauf définir ou obtenir sont probablement trop de tests, pas besoin de prendre le temps. À moins que votre IDE ne les écrive pour vous, alors vous pourriez aussi bien.

    Si votre constructeur ne définissant pas de propriétés risque de générer des erreurs ultérieurement, les tests effectués ne sont pas excessifs.

    J’écris des tests pour couvrir les hypothèses des classes que je vais écrire. Les tests imposent les exigences. Essentiellement, si x ne peut jamais être 3, par exemple, je vais faire en sorte qu’un test couvre cette exigence.

    Invariablement, si je n’écris pas de test pour couvrir une condition, cela apparaîtra plus tard lors des tests «humains». Je vais certainement en écrire un, mais je préfère les attraper tôt. Je pense que le fait est que les tests sont fastidieux (peut-être) mais nécessaires. J’écris assez de tests pour être complet mais pas plus que ça.

    Une partie du problème de sauter des tests simples maintenant est que le refactoring pourrait compliquer cette propriété simple avec beaucoup de logique. Je pense que la meilleure idée est que vous pouvez utiliser des tests pour vérifier les exigences du module. Si vous passez X, vous devriez récupérer Y, alors c’est ce que vous voulez tester. Ensuite, lorsque vous modifiez le code ultérieurement, vous pouvez vérifier que X vous donne Y, et vous pouvez append un test pour que A vous donne B, lorsque cette exigence est ajoutée ultérieurement.

    J’ai constaté que le temps passé lors des premiers tests d’écriture de développement est rentable dans le premier ou le second correctif. La possibilité de prendre le code que vous n’avez pas regardé depuis 3 mois et d’être raisonnablement sûr que votre correction couvre tous les cas, et “probablement” ne casse rien est extrêmement précieuse. Vous constaterez également que les tests unitaires aideront à sortinger les bogues bien au-delà de la trace de la stack, etc. En voyant comment les différentes parties de l’application fonctionnent et échouent, vous comprendrez pourquoi elles fonctionnent ou échouent dans leur ensemble.

    Dans la plupart des cas, je dirais, s’il y a une logique là-bas, testez-la. Cela inclut les constructeurs et les propriétés, en particulier lorsque plusieurs objects sont définis dans la propriété.

    En ce qui concerne trop d’essais, c’est discutable. Certains diraient que tout devrait être testé pour la robustesse, d’autres disent que pour des tests efficaces, seules les choses qui pourraient se casser (c’est-à-dire la logique) devraient être testées.

    Je me pencherais davantage sur le deuxième camp, uniquement sur la base de mon expérience personnelle, mais si quelqu’un décidait de tout tester, je ne dirais pas que c’était trop… peut-être un peu trop pour moi, mais pas trop pour eux.

    Donc, non, je dirais qu’il n’y a pas de tests “trop” au sens général, mais uniquement pour les individus.

    Le développement piloté par les tests signifie que vous arrêtez de coder lorsque tous vos tests réussissent.

    Si vous n’avez aucun test pour une propriété, alors pourquoi devriez-vous la mettre en œuvre? Si vous ne testez / ne définissez pas le comportement attendu en cas d’affectation “illégale”, que devrait faire la propriété?

    Par conséquent, je suis totalement pour tester chaque comportement qu’une classe doit présenter. Y compris les propriétés “primitives”.

    Pour faciliter ce test, j’ai créé un simple NUnit TestFixture qui fournit des points d’extension pour définir / obtenir la valeur et prend des listes de valeurs valides et non valides et dispose d’un seul test pour vérifier si la propriété fonctionne correctement. Tester une seule propriété pourrait ressembler à ceci:

     [TestFixture] public class Test_MyObject_SomeProperty : PropertyTest { private MyObject obj = null; public override void SetUp() { obj = new MyObject(); } public override void TearDown() { obj = null; } public override int Get() { return obj.SomeProperty; } public override Set(int value) { obj.SomeProperty = value; } public override IEnumerable SomeValidValues() { return new List() { 1,3,5,7 }; } public override IEnumerable SomeInvalidValues() { return new List() { 2,4,6 }; } } 

    En utilisant lambdas et les atsortingbuts, cela pourrait même être écrit de manière plus compacte. Je suppose que MBUnit a même un support natif pour des choses comme ça. Le point est que le code ci-dessus capture l’intention de la propriété.

    PS: Probablement le PropertyTest devrait aussi avoir un moyen de vérifier que les autres propriétés de l’object n’ont pas changé. Hmm .. retour à la planche à dessin.

    Je fais des tests unitaires pour atteindre la couverture maximale possible. Si je ne parviens pas à atteindre un code, je le refactore jusqu’à ce que la couverture soit aussi complète que possible

    Après avoir terminé le test d’écriture aveuglant, j’écris généralement un cas de test reproduisant chaque bogue

    Je suis habitué à séparer les tests de code et les tests d’intégration. Pendant les tests d’intégration (qui sont également des tests unitaires mais sur des groupes de composants, donc pas exactement ce que sont les tests unitaires), je testerai si les exigences doivent être correctement implémentées.

    Donc, plus je pilote ma programmation en écrivant des tests, moins je m’inquiète du niveau de granularité des tests. En regardant en arrière, il semble que je fasse la chose la plus simple possible pour atteindre mon objective de validation du comportement . Cela signifie que je suis sûr que mon code fait ce que je demande, mais cela ne signifie pas que mon code est exempt de bogues. Je pense que le bon équilibre consiste à tester le comportement standard et peut-être un ou deux cas en bordure puis à passer à la partie suivante de mon dessin.

    J’accepte que cela ne couvre pas tous les bugs et utilise d’autres méthodes de test traditionnelles pour les capturer.

    En général, je commence petit, avec des intrants et des extrants que je sais devoir travailler. Alors que je corrige des bogues, j’ajoute d’autres tests pour vérifier que les choses que j’ai corrigées ont été testées. C’est organique et ça marche bien pour moi.

    Pouvez-vous tester trop? Probablement, mais il est probablement préférable de privilégier la prudence en général, bien que cela dépende de la nature critique de votre application.

    Je pense que vous devez tout tester dans votre “kernel” de votre logique métier. Getter ans Setter aussi, car ils pourraient accepter une valeur négative ou une valeur nulle que vous ne voudriez peut-être pas accepter. Si vous avez le temps (dépend toujours de votre patron), il est bon de tester une autre logique métier et tous les contrôleurs qui appellent ces objects (vous passez du test unitaire au test d’intégration lentement).

    Je ne teste pas les méthodes simples de setter / getter sans effets secondaires. Mais je ne fais que tester toutes les autres méthodes publiques. J’essaie de créer des tests pour toutes les conditions limites dans mes algorthims et de vérifier la couverture de mes tests unitaires.

    C’est beaucoup de travail mais je pense que ça en vaut la peine. Je préfère écrire du code (même pour tester le code) que de parcourir le code dans un débogueur. Je trouve que le cycle code-build-deploy-debug prend beaucoup de temps et plus les tests unitaires que j’ai intégrés dans ma construction sont exhaustifs, moins je passe de temps à parcourir ce cycle code-build-deploy-debug.

    Vous n’avez pas dit pourquoi l’architecture que vous codez aussi. Mais pour Java, j’utilise Maven 2 , JUnit , DbUnit , Cobertura et EasyMock .

    Plus je lis à ce sujet, plus je pense que certains tests unitaires sont comme certains modèles: une odeur de langue insuffisante.

    Lorsque vous avez besoin de tester si votre getter sortingvial retourne réellement la bonne valeur, c’est parce que vous pouvez mélanger le nom du getter et celui de la variable du membre. Entrez ‘attr_reader: name’ de ruby, et cela ne peut plus arriver. Juste pas possible en Java.

    Si votre getter n’est jamais indifférent, vous pouvez toujours y append un test.

    Testez le code source qui vous inquiète.

    N’est pas utile de tester des portions de code dans lesquelles vous êtes très confiant, à condition de ne pas commettre d’erreurs.

    Testez les corrections de bogues, pour que ce soit la première et la dernière fois que vous corrigez un bogue.

    Testez pour obtenir la confiance de portions de code obscures, afin de créer des connaissances.

    Testez avant de refactoriser lourd et moyen, afin de ne pas casser les fonctionnalités existantes.

    Cette réponse est plus utile pour déterminer le nombre de tests unitaires à utiliser pour une méthode donnée que vous savez que vous souhaitez tester à cause de sa criticité / importance. En utilisant la technique de test de chemin de base de McCabe, vous pouvez faire ce qui suit pour avoir une meilleure confiance en couverture de code que la simple “couverture de relevé” ou la “couverture de twig”:

    1. Déterminez la valeur de complexité cyclomatique de votre méthode à tester (Visual Studio 2010 Ultimate, par exemple, peut le calculer avec des outils d’parsing statique; sinon, vous pouvez le calculer manuellement à l’aide de la méthode flowgraph – http://users.csc. calpoly.edu/~jdalbey/206/Lectures/BasisPathTutorial/index.html )
    2. Énumérer l’ensemble des chemins indépendants qui circulent dans votre méthode – voir le lien ci-dessus pour l’exemple de diagramme
    3. Préparer des tests unitaires pour chaque chemin de base indépendant déterminé à l’étape 2