Ce que j'ai trouvé sur TDD, c'est qu'il faut du temps pour configurer vos tests et, étant naturellement paresseux, je veux toujours écrire le moins de code possible. La première chose que je semble faire est de tester que mon constructeur a défini toutes les propriétés, mais est-ce exagéré?
Ma question est de savoir à quel niveau de granularité écrivez-vous vos tests unitaires?
..et y a-t-il trop de tests?
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 soupçonne que ce niveau de confiance est élevé par rapport aux normes de l'industrie, mais cela pourrait juste être un orgueil) . Si je ne fais généralement pas 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, donc je suis très prudent lorsque 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, collectivement, avons tendance à se tromper.
Différentes personnes auront différentes stratégies de test basées sur cette philosophie, mais cela me semble raisonnable étant donné l'état de compréhension immature de la meilleure façon dont les tests peuvent s'intégrer dans la boucle interne du codage. Dans dix ou vingt ans, nous aurons probablement une théorie plus universelle sur les tests à écrire, les tests à ne pas écrire et comment faire la différence. En attendant, l'expérimentation semble de mise.
Écrivez des tests unitaires pour les éléments que vous prévoyez de casser et pour les cas Edge. Après cela, des cas de test doivent être ajoutés à mesure que les rapports de bogue arrivent - avant d'écrire le correctif du bogue. Le développeur peut alors être convaincu que:
Selon le commentaire ci-joint - je suppose que cette approche pour écrire des tests unitaires pourrait causer des problèmes, si de nombreux bogues sont, au fil du temps, découverts 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 graves problèmes. J'ai trouvé qu'une mesure des tests d'intégration dans les tests unitaires peut être utile dans ces scénarios - le test de code plus haut codepaths peut couvrir les codepaths plus bas.
Tout doit être aussi simple que possible, mais pas plus simple. - A. Einstein
L'une des choses les plus mal comprises à propos de TDD est le premier mot qu'il contient. Tester. C'est pourquoi BDD est venu. Parce que les gens ne comprenaient pas vraiment que le premier D était important, à savoir Driven. Nous avons tous tendance à penser un peu à beaucoup aux tests et à la conduite du design. Et je suppose que c'est une réponse vague à votre question, mais vous devriez probablement réfléchir à la façon de piloter 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 beaucoup plus important et problématique.
Pour ceux qui proposent de tester "tout": réalisez que "tester complètement" une méthode comme int square(int x)
nécessite environ 4 milliards de cas de test 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 pas de modifier les valeurs de tout autre membre que x
- testez-vous que obj.y
, obj.z
, etc. restent 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 un comportement incroyablement basique. Chaque programmeur a une distribution de probabilité des emplacements des bogues; 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 "tester tout ce qui pourrait éventuellement casser". J'interprète cela comme signifiant que tester les setters et les getters qui ne font rien sauf set ou get est probablement trop de tests, pas besoin de prendre le temps. À moins que votre IDE ne les écrive pour vous, alors vous aussi.
Si votre constructeur pas la définition des propriétés pourrait conduire à des erreurs plus tard, alors vérifier qu'elles sont définies n'est pas exagéré.
J'écris des tests pour couvrir les hypothèses des classes que j'écrirai. Les tests font respecter les exigences. Essentiellement, si x ne peut jamais être 3, par exemple, je vais m'assurer qu'il existe un test qui couvre cette exigence.
Invariablement, si je n'écris pas de test pour couvrir une condition, il réapparaîtra plus tard lors des tests "humains". Je vais certainement en écrire un alors, 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.
Une partie du problème de sauter des tests simples maintenant est qu'à l'avenir, le refactoring pourrait rendre cette propriété simple très compliquée 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 lorsque vous passez X, vous devriez récupérer Y, alors c'est ce que vous voulez tester. Ensuite, lorsque vous modifiez le code plus tard, vous pouvez vérifier que X vous donne Y et vous pouvez ajouter un test pour A vous donne B, lorsque cette exigence est ajoutée plus tard.
J'ai constaté que le temps que je passe pendant les tests d'écriture de développement initial porte ses fruits dans le premier ou le deuxième correctif de bogue. La possibilité de récupérer du code que vous n'avez pas regardé depuis 3 mois et d'être raisonnablement sûr que votre correctif couvre tous les cas, et "probablement" ne casse rien est extrêmement précieuse. Vous constaterez également que les tests unitaires aideront à trier les bogues bien au-delà de la trace de la pile, etc. Voir comment des pièces individuelles de l'application fonctionnent et échouent donne un énorme aperçu de la raison pour laquelle elles fonctionnent ou échouent dans leur ensemble.
Dans la plupart des cas, je dirais que s'il y a de la logique, testez-la. Cela inclut les constructeurs et les propriétés, en particulier lorsque plusieurs éléments sont définis dans la propriété.
En ce qui concerne trop de tests, 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 casser (c'est-à-dire la logique) devraient être testées.
Je pencherais davantage vers le deuxième camp, juste par expérience personnelle, mais si quelqu'un décidait de tout tester, je ne dirais pas que c'était trop ... un peu exagéré peut-être pour moi, mais pas trop pour eux.
Donc, non - je dirais qu'il n'y a pas de test "trop" au sens général, seulement 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 l'implémenter? Si vous ne testez/définissez pas le comportement attendu en cas d'affectation "illégale", que doit 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 invalides et a un seul test pour vérifier si la propriété fonctionne correctement. Le test d'une seule propriété pourrait ressembler à ceci:
[TestFixture]
public class Test_MyObject_SomeProperty : PropertyTest<int>
{
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<int> SomeValidValues() { return new List() { 1,3,5,7 }; }
public override IEnumerable<int> SomeInvalidValues() { return new List() { 2,4,6 }; }
}
L'utilisation de lambdas et d'attributs pourrait même être écrite de manière plus compacte. Je suppose que MBUnit a même un support natif pour des choses comme ça. Le fait est que le code ci-dessus capture l'intention de la propriété.
P.S .: Probablement, le PropertyTest devrait également avoir un moyen de vérifier que les propriétés other sur l'objet 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 refaçonne jusqu'à ce que la couverture soit aussi complète que possible
Après avoir terminé le test d'écriture aveuglante, j'écris généralement un cas de test reproduisant chaque bogue
J'ai l'habitude de faire la distinction entre 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 à quoi servent les tests unitaires), je testerai les exigences à implémenter correctement.
Donc, plus je pilote ma programmation en écrivant des tests, moins je m'inquiète du niveau de granularité des tests. Avec le recul, il semble que je fais la chose la plus simple possible pour atteindre mon objectif de validation comportement. Cela signifie que je génère une couche de confiance que mon code fait ce que je demande de faire, mais cela n'est pas considéré comme une garantie absolue 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 Edge, puis passer à la partie suivante de ma conception.
J'accepte que cela ne couvre pas tous les bogues et utilise d'autres méthodes de test traditionnelles pour les capturer.
Cette réponse est plus pour déterminer le nombre de tests unitaires à utiliser pour une méthode donnée que vous savez que vous souhaitez tester en raison de sa criticité/importance. En utilisant la technique de test de chemin de base de McCabe, vous pouvez effectuer les opérations suivantes pour avoir une meilleure assurance de la couverture du code que la simple "couverture d'instruction" ou "couverture de branche":
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, tant que vous n'y faites pas d'erreurs.
Testez les corrections de bogues, afin que ce soit la première et la dernière fois que vous corrigez un bogue.
Testez pour obtenir la confiance des parties de code obscures, afin de créer des connaissances.
Testez avant une refactorisation lourde et moyenne, afin de ne pas casser les fonctionnalités existantes.
En général, je commence petit, avec des entrées et des sorties qui, je le sais, doivent fonctionner. Ensuite, lorsque je corrige des bugs, j'ajoute d'autres tests pour m'assurer que les choses que j'ai corrigées sont testées. C'est organique et ça marche bien pour moi.
Pouvez-vous trop tester? Probablement, mais il vaut probablement mieux pécher par excès de prudence en général, bien que cela dépende du degré de mission critique de votre application.
Plus j'en lis, plus je pense que certains tests unitaires sont comme certains modèles: une odeur de langues insuffisantes.
Lorsque vous devez tester si votre getter trivial renvoie réellement la bonne valeur, c'est parce que vous pouvez mélanger le nom du getter et le nom de la variable membre. Entrez 'attr_reader: name' de Ruby, et cela ne peut plus se produire. Tout simplement pas possible en Java.
Si votre getter ne devient jamais trivial, vous pouvez toujours lui ajouter un test.
Je pense que vous devez tout tester dans votre "noyau" de votre logique métier. Getter et Setter aussi parce qu'ils pourraient accepter une valeur négative ou une valeur nulle que vous pourriez ne pas vouloir accepter. Si vous avez le temps (toujours dépendant de votre patron), il est bon de tester d'autres logiques métier et tous les contrôleurs qui appellent ces objets (vous passez lentement du test unitaire au test d'intégration).
Je ne fais pas de tests unitaires de méthodes simples setter/getter qui n'ont pas d'effets secondaires. Mais je fais des tests unitaires sur toutes les autres méthodes publiques. J'essaie de créer des tests pour toutes les conditions aux limites dans mes algorthims et de vérifier la couverture de mes tests unitaires.
C'est beaucoup de travail mais je pense que ça vaut le coup. Je préfère écrire du code (même tester du code) que de parcourir le code dans un débogueur. Je trouve que le cycle de construction de code-déploiement-débogage prend beaucoup de temps et plus les tests unitaires que j'ai intégrés dans ma génération sont exhaustifs, moins je passe de temps à travers ce cycle de construction de code-déploiement-débogage.
Vous n'avez pas dit pourquoi l'architecture que vous codez aussi. Mais pour Java j'utilise Maven 2 , JUnit , DbUnit , Cobertura , & EasyMock .