J'essaie de rendre mon code plus robuste et j'ai lu sur les tests unitaires, mais je trouve très difficile de trouver une utilisation réellement utile. Par exemple, l'exemple Wikipedia :
public class TestAdder {
public void testSum() {
Adder adder = new AdderImpl();
assert(adder.add(1, 1) == 2);
assert(adder.add(1, 2) == 3);
assert(adder.add(2, 2) == 4);
assert(adder.add(0, 0) == 0);
assert(adder.add(-1, -2) == -3);
assert(adder.add(-1, 1) == 0);
assert(adder.add(1234, 988) == 2222);
}
}
Je pense que ce test est totalement inutile, car vous devez calculer manuellement le résultat souhaité et le tester, je pense qu'un meilleur test unitaire serait ici
assert(adder.add(a, b) == (a+b));
mais alors c'est juste coder la fonction elle-même dans le test. Quelqu'un peut-il me donner un exemple où les tests unitaires sont réellement utiles? Pour info je suis en train de coder principalement des fonctions "procédurales" qui prennent ~ 10 booléens et quelques ints et me donnent un résultat int basé sur cela, je pense que le seul test unitaire que je pourrais faire serait de simplement recoder l'algorithme dans le tester. edit: j'aurais également dû préciser que c'est lors du portage (peut-être mal conçu) du code ruby (que je n'ai pas fait)
la source
How does unit testing work?
Personne ne sait vraiment :)Réponses:
Les tests unitaires, si vous testez des unités suffisamment petites, affirment toujours ce qui est aveuglément évident.
La raison qui fait
add(x, y)
même mention d'un test unitaire est que, quelque temps plus tard, quelqu'un entreraadd
et mettra un code de gestion de logique fiscale spécial ne réalisant pas que l'ajout est utilisé partout.Les tests unitaires sont très bien sur le principe associatif: si A ne B, et B fait C, alors A fait C. « A fait C » est un test de niveau supérieur. Par exemple, considérez le code d'entreprise suivant, tout à fait légitime:
À première vue, cela ressemble à une méthode géniale de test unitaire, car elle a un objectif très clair. Cependant, il fait environ 5 choses différentes. Chaque chose a un cas valide et invalide, et fera une énorme permutation de tests unitaires. Idéalement, cela se décompose davantage:
Nous en sommes maintenant aux unités. Un test unitaire ne se soucie pas de ce qui
_userRepo
considère un comportement valideFetchValidUser
, seulement qu'il est appelé. Vous pouvez utiliser un autre test pour garantir exactement ce que constitue un utilisateur valide. De même pourCheckUserForRole
... vous avez découplé votre test de savoir à quoi ressemble la structure de rôle. Vous avez également découplé l'intégralité de votre programmeSession
. J'imagine que toutes les pièces manquantes ici ressembleraient à:En refactorisant, vous avez accompli plusieurs choses à la fois. Le programme est beaucoup plus favorable à la suppression des structures sous-jacentes (vous pouvez abandonner la couche de base de données pour NoSQL), ou à ajouter de façon transparente un verrouillage une fois que vous réalisez que ce
Session
n'est pas thread-safe ou autre. Vous vous êtes également donné des tests très simples à écrire pour ces trois dépendances.J'espère que cela t'aides :)
la source
Je suis à peu près sûr que chacune de vos fonctions procédurales est déterministe, elle renvoie donc un résultat int spécifique pour chaque ensemble de valeurs d'entrée donné. Idéalement, vous auriez une spécification fonctionnelle à partir de laquelle vous pouvez déterminer le résultat que vous devriez recevoir pour certains ensembles de valeurs d'entrée. En l'absence de cela, vous pouvez exécuter le code ruby (qui est supposé fonctionner correctement) pour certains ensembles de valeurs d'entrée et enregistrer les résultats. Ensuite, vous devez coder les résultats dans votre test. Le test est censé être une preuve que votre code produit effectivement des résultats connus pour être corrects .
la source
Étant donné que personne d'autre ne semble avoir fourni un exemple réel:
Vous dites:
Mais le fait est que vous êtes beaucoup moins susceptible de faire une erreur (ou du moins plus susceptible de remarquer vos erreurs) lors des calculs manuels, puis lors du codage.
Quelle est la probabilité de faire une erreur dans votre code de conversion décimal en romain? Probablement. Quelle est la probabilité de faire une erreur lors de la conversion manuelle des nombres décimaux en chiffres romains? Probablement pas. C'est pourquoi nous testons par rapport aux calculs manuels.
Quelle est la probabilité de faire une erreur lors de l'implémentation d'une fonction de racine carrée? Probablement. Quelle est la probabilité que vous vous trompiez lors du calcul manuel d'une racine carrée? Probablement plus probable. Mais avec sqrt, vous pouvez utiliser une calculatrice pour obtenir les réponses.
Je vais donc spéculer sur ce qui se passe ici. Vos fonctions sont un peu compliquées, il est donc difficile de déterminer à partir des entrées ce que devrait être la sortie. Pour ce faire, vous devez exécuter manuellement (dans votre tête) la fonction pour déterminer la sortie. Naturellement, cela semble un peu inutile et sujet aux erreurs.
La clé est que vous voulez trouver les bonnes sorties. Mais vous devez tester ces sorties par rapport à quelque chose de correct. Ce n'est pas bon d'écrire votre propre algorithme pour le calculer car cela peut très bien être incorrect. Dans ce cas, il est trop difficile de calculer manuellement les valeurs.
Je reviendrais au code ruby et exécuterais ces fonctions originales avec divers paramètres. Je prendrais les résultats du code rubis et les mettrais dans le test unitaire. De cette façon, vous n'avez pas à faire le calcul manuel. Mais vous testez par rapport au code d'origine. Cela devrait aider à garder les mêmes résultats, mais s'il y a des bogues dans l'original, cela ne vous aidera pas. Fondamentalement, vous pouvez traiter le code d'origine comme la calculatrice dans l'exemple sqrt.
Si vous montriez le code réel que vous portez, nous pourrions fournir des commentaires plus détaillés sur la façon d'aborder le problème.
la source
Vous avez presque raison pour une classe aussi simple.
Essayez-le pour une calculatrice plus complexe. Comme une calculatrice de score de bowling.
La valeur des tests unitaires est plus facilement visible lorsque vous avez des règles "métier" plus complexes avec différents scénarios à tester.
Je ne dis pas que vous ne devriez pas tester une course de la calculatrice de l'usine (votre compte de calculatrice émet-il des valeurs comme 1/3 qui ne peuvent pas être représentées? Que fait-il avec la division par zéro?), Mais vous verrez le évaluer plus clairement si vous testez quelque chose avec plus de succursales pour obtenir une couverture.
la source
Malgré le fanatisme religieux d'environ 100% de couverture de code, je dirai que toutes les méthodes ne doivent pas être testées à l'unité. Seule fonctionnalité qui contient une logique métier significative. Une fonction qui ajoute simplement un nombre est inutile à tester.
Il y a là votre vrai problème. Si les tests unitaires semblent anormalement durs ou inutiles, cela est probablement dû à un défaut de conception. Si elle était plus orientée objet, vos signatures de méthode ne seraient pas aussi massives et il y aurait moins d'entrées possibles à tester.
Je n'ai pas besoin d'entrer dans mon OO est supérieur à la programmation procédurale ...
la source
De mon point de vue, les tests unitaires sont même utiles à votre petite classe d'additionneur: ne pensez pas à "recoder" l'algorithme et considérez-le comme une boîte noire avec la seule connaissance que vous avez sur le comportement fonctionnel (si vous êtes familier avec une multiplication rapide, vous connaissez des tentatives plus rapides mais plus complexes que l'utilisation du "a * b") et de votre interface publique. Alors vous devriez vous demander "Qu'est-ce qui pourrait bien se passer?" ...
Dans la plupart des cas, cela se produit à la frontière (je vois que vous testez déjà l'ajout de ces modèles ++, -, + -, 00 - temps pour les compléter par - +, 0+, 0-, +0, -0). Pensez à ce qui se passe à MAX_INT et MIN_INT lors de l'ajout ou de la soustraction (ajout de négatifs;)). Ou essayez de vous assurer que vos tests ressemblent exactement à ce qui se passe à zéro ou autour de zéro.
Dans l'ensemble, le secret est très simple (peut-être aussi pour les plus complexes;)) pour les classes simples: pensez aux contrats de votre classe (voir conception par contrat) puis testez-les. Mieux vous connaîtrez vos invs, pre's et post's, le "completer" sera vos tests.
Astuce pour vos classes de test: essayez d'écrire une seule assertion dans une méthode. Donnez de bons noms aux méthodes (par exemple, "testAddingToMaxInt", "testAddingTwoNegatives") pour avoir les meilleurs commentaires lorsque votre test échoue après le changement de code.
la source
Plutôt que de tester une valeur de retour calculée manuellement ou de dupliquer la logique dans le test pour calculer la valeur de retour attendue, testez la valeur de retour pour une propriété attendue.
Par exemple, si vous voulez tester une méthode qui inverse une matrice, vous ne voulez pas inverser manuellement votre valeur d'entrée, vous devez multiplier la valeur de retour par l'entrée et vérifier que vous obtenez la matrice d'identité.
Pour appliquer cette approche à votre méthode, vous devrez tenir compte de son objectif et de sa sémantique, afin d'identifier les propriétés de la valeur de retour par rapport aux entrées.
la source
Les tests unitaires sont un outil de productivité. Vous recevez une demande de modification, implémentez-la, puis exécutez votre code via le gambit de test unitaire. Ces tests automatisés font gagner du temps.
I feel that this test is totally useless, because you are required to manually compute the wanted result and test it, I feel like a better unit test here would be
Un point discutable. Le test de l'exemple montre simplement comment instancier une classe et l'exécuter à travers une série de tests. Se concentrer sur la minutie d'une seule mise en œuvre manque la forêt pour les arbres.
Can someone provide me with an example where unit testing is actually useful?
Vous avez une entité Employé. L'entité contient un nom et une adresse. Le client décide d'ajouter un champ ReportsTo.
C'est un test de base du BL pour travailler avec un employé. Le code passera / échouera la modification de schéma que vous venez d'apporter. N'oubliez pas que les affirmations ne sont pas la seule chose que fait le test. L'exécution du code garantit également qu'aucune exception ne se propage.
Au fil du temps, la mise en place des tests facilite les changements en général. Le code est automatiquement testé pour les exceptions et les assertions que vous faites. Cela évite une grande partie des frais généraux occasionnés par les tests manuels par un groupe d'assurance qualité. Bien que l'interface utilisateur soit encore assez difficile à automatiser, les autres couches sont généralement très faciles en supposant que vous utilisez correctement les modificateurs d'accès.
I feel like the only unit testing I could do would be to simply re-code the algorithm in the test.
Même la logique procédurale est facilement encapsulée dans une fonction. Encapsuler, instancier et passer l'int / primitive à tester (ou l'objet factice). Ne copiez pas collez le code dans un test unitaire. Cela bat DRY. Il échoue également entièrement au test car vous ne testez pas le code, mais une copie du code. Si le code qui aurait dû être testé change, le test réussit quand même!
la source
Prenant votre exemple (avec un peu de refactoring),
n'aide pas à:
math.add
se comporte en interne,Cela revient à dire:
math.add
peut contenir des centaines de LOC; voir ci-dessous).Cela signifie également que vous n'avez pas besoin d'ajouter des tests comme:
Ils n'aident pas non plus, ou du moins, une fois que vous avez fait la première affirmation, la seconde n'apporte rien d'utile.
Au lieu de cela, qu'en est-il:
Cela s'explique de lui-même et est extrêmement utile pour vous et pour la personne qui conservera le code source plus tard. Imaginez que cette personne modifie légèrement le
math.add
pour simplifier le code et optimiser les performances, et voit le résultat du test comme:cette personne comprendra immédiatement que la méthode nouvellement modifiée dépend de l'ordre des arguments: si le premier argument est un entier et le second est un long numérique, le résultat serait un entier, alors qu'un long numérique était attendu.
De la même manière, l'obtention de la valeur réelle de
4.141592
lors de la première assertion est explicite: vous savez que la méthode est censée traiter avec une grande précision , mais en fait, elle échoue.Pour la même raison, deux affirmations suivantes peuvent avoir un sens dans certaines langues:
Et qu'en est-il de:
Explicite aussi: vous voulez que votre méthode puisse gérer correctement l'infini. Aller au-delà de l'infini ou lever une exception n'est pas un comportement attendu.
Ou peut-être, selon votre langue, cela aura plus de sens?
la source
Pour une fonction très simple comme add, les tests peuvent être considérés comme inutiles, mais à mesure que vos fonctions deviennent plus complexes, il devient de plus en plus évident pourquoi les tests sont nécessaires.
Pensez à ce que vous faites lorsque vous programmez (sans test unitaire). Habituellement, vous écrivez du code, l'exécutez, voyez qu'il fonctionne et passez à la chose suivante, non? Au fur et à mesure que vous écrivez plus de code, en particulier dans un très grand système / interface graphique / site Web, vous constatez que vous devez faire de plus en plus de "courir et voir si cela fonctionne". Vous devez essayer ceci et essayer cela. Ensuite, vous apportez quelques modifications et vous devez recommencer ces mêmes choses. Il devient très évident que vous pourriez gagner du temps en écrivant des tests unitaires qui automatiseraient toute la partie "courir et voir si ça marche".
À mesure que vos projets deviennent de plus en plus grands, le nombre de choses que vous devez "exécuter et voir si cela fonctionne" devient irréaliste. Donc, vous finissez par exécuter et essayer quelques composants majeurs de l'interface graphique / projet et en espérant que tout le reste va bien. Ceci est une recette pour le désastre. Bien sûr, en tant qu'être humain, vous ne pouvez pas tester de manière répétée chaque situation possible que vos clients pourraient utiliser si l'interface graphique est utilisée par des centaines de personnes. Si vous aviez des tests unitaires en place, vous pouvez simplement exécuter le test avant d'expédier la version stable, ou même avant de vous engager dans le référentiel central (si votre lieu de travail en utilise un). Et, s'il y a des bogues trouvés plus tard, vous pouvez simplement ajouter un test unitaire pour le vérifier à l'avenir.
la source
L'un des avantages de l'écriture de tests unitaires est qu'il vous aide à écrire du code plus robuste en vous forçant à penser aux cas limites. Que diriez-vous de tester certains cas marginaux, comme le débordement d'entier, la troncature décimale ou la gestion des valeurs nulles pour les paramètres?
la source
Vous supposez peut-être que add () a été implémenté avec l'instruction ADD. Si un programmeur junior ou un ingénieur matériel a réimplémenté la fonction add () à l'aide d'ANDS / ORS / XORS, des inversions de bits et des décalages, vous voudrez peut-être le tester unitaire par rapport à l'instruction ADD.
En général, si vous remplacez les tripes d'add () ou de l'unité testée par un nombre aléatoire ou un générateur de sortie, comment sauriez-vous que quelque chose a été cassé? Encodez cette connaissance dans vos tests unitaires. Si personne ne peut dire s'il est cassé, vérifiez simplement le code de rand () et rentrez chez vous, votre travail est terminé.
la source
Je l'ai peut-être manqué dans toutes les réponses, mais pour moi, le principal moteur des tests unitaires consiste moins à prouver l'exactitude d'une méthode aujourd'hui, mais à prouver l'exactitude continue de cette méthode lorsque vous la modifiez .
Prenez une fonction simple, comme renvoyer le nombre d'articles dans une collection. Aujourd'hui, lorsque votre liste est basée sur une structure de données interne que vous connaissez bien, vous pourriez penser que cette méthode est si douloureusement évidente que vous n'avez pas besoin de la tester. Ensuite, dans plusieurs mois ou années, vous (ou quelqu'un d'autre ) décidez de remplacer la structure de liste interne. Vous devez toujours savoir que getCount () renvoie la valeur correcte.
C'est là que vos tests unitaires prennent tout leur sens.
Vous pouvez modifier l'implémentation interne de votre code, mais pour tout consommateur de ce code, le résultat reste le même.
la source