Déterminer ce qu'est un test unitaire utile

47

J'ai parcouru la documentation de phpunit et suis tombé sur la citation suivante:

Vous pouvez toujours écrire plus de tests. Cependant, vous constaterez rapidement que seule une fraction des tests que vous pouvez imaginer est réellement utile. Ce que vous voulez, c'est écrire des tests qui échouent même si vous pensez qu'ils doivent fonctionner, ou des tests qui réussissent même si vous pensez qu'ils doivent échouer. Une autre façon de penser est en termes de coûts / avantages. Vous voulez écrire des tests qui vous rembourseront avec des informations. --Erich Gamma

Cela m'a fait me demander. Comment déterminez-vous ce qui rend un test unitaire plus utile qu'un autre, mis à part ce qui est indiqué dans cette citation sur les coûts / avantages. Comment décidez-vous pour quelle partie de votre code vous créez des tests unitaires? Je demande cela parce qu'une autre de ces citations a également dit:

Donc, s'il ne s'agit pas de tests, de quoi s'agit-il? Il s'agit de déterminer ce que vous essayez de faire avant de vous lancer à moitié armé pour essayer de le faire. Vous écrivez une spécification qui décrit un petit aspect du comportement sous une forme concise, non ambiguë et exécutable. C'est si simple. Est-ce que cela signifie que vous écrivez des tests? Non, cela signifie que vous écrivez des spécifications de ce que votre code devra faire. Cela signifie que vous spécifiez le comportement de votre code à l'avance. Mais pas très en avance sur le temps. En fait, le mieux est d’écrire le code, c’est parce que vous avez le maximum d’informations sous la main. Comme TDD bien fait, vous travaillez par petites étapes ... en spécifiant un petit aspect du comportement à la fois, puis en le mettant en œuvre. Lorsque vous réalisez qu'il s'agit de spécifier un comportement et non d'écrire des tests, votre point de vue change. Soudainement, l'idée d'avoir une classe de test pour chacune de vos classes de production est ridiculement limitante. Et l'idée de tester chacune de vos méthodes avec sa propre méthode de test (dans une relation 1-1) sera risible. --Dave Astels

La partie importante de cela est

* Et l'idée de tester chacune de vos méthodes avec sa propre méthode de test (dans une relation 1-1) sera risible. *

Donc, si créer un test pour chaque méthode est "risible", comment / quand avez-vous choisi ce pour quoi vous écrivez des tests?

zourts
la source
5
C'est une bonne question, mais je pense que c'est une question indépendante du langage de programmation - pourquoi l'avez-vous tagué php?
Voici quelques très bons articles qui m'ont fourni une bonne orientation sur les tests unitaires que je devrais écrire et sur les domaines dans lesquels il est important de fournir des tests automatisés. - blog.stevensanderson.com/2009/11/04/… - blog.stevensanderson.com/2009/08/24/… - ayende.com/blog/4218/scenario-driven-tests
Kane

Réponses:

27

Combien de tests par méthode?

Eh bien, le maximum théorique et hautement impraticable est la complexité de N-Path (supposons que les tests couvrent tous différentes manières dans le code;)). Le minimum est UN !. Par méthode publique , il ne teste pas les détails de l'implémentation, mais uniquement les comportements externes d'une classe (renvoie des valeurs et appelle d'autres objets).

Vous citez:

* Et l'idée de tester chacune de vos méthodes avec sa propre méthode de test (dans une relation 1-1) sera risible. *

et ensuite demander:

Donc, si créer un test pour chaque méthode est "risible", comment / quand avez-vous choisi ce pour quoi vous écrivez des tests?

Mais je pense que vous avez mal compris l'auteur ici:

L'idée d'avoir one test methodpar one method in the class to testce que l'auteur appelle « risible ».

(Pour moi au moins) Il ne s'agit pas de "moins" mais de "plus"

Alors laissez-moi reformuler comme je l'ai compris:

Et l'idée de tester chacune de vos méthodes avec ONLY ONE METHOD (sa propre méthode de test dans une relation 1-1) sera risible.

Pour citer à nouveau votre devis:

Lorsque vous réalisez qu'il ne s'agit que de spécifier un comportement et non d'écrire des tests, votre point de vue change.


Lorsque vous pratiquez le TDD, vous ne pensez pas :

J'ai une méthode calculateX($a, $b);et il faut un test testCalculcateXqui teste TOUT sur la méthode.

Ce que TDD vous dit, c'est de réfléchir à ce que votre code DEVRAIT FAIRE :

J'ai besoin de calculer la plus grande des deux valeurs ( premier cas de test! ), Mais si $ a est inférieur à zéro, il devrait générer une erreur ( deuxième cas de test! ) Et si $ b est inférieur à zéro, il devrait ... ( troisième cas de test! ) et ainsi de suite.


Vous voulez tester des comportements, pas seulement des méthodes uniques sans contexte.

De cette façon, vous obtenez une suite de tests qui est une documentation pour votre code et explique VRAIMENT ce qu’elle est censée faire, peut-être même pourquoi :)


Comment décidez-vous pour quelle partie de votre code vous créez des tests unitaires?

Eh bien, tout ce qui finit dans le référentiel ou n'importe où près de la production nécessite un test. Je ne pense pas que l'auteur de vos citations serait en désaccord avec cela car j'ai essayé de le dire plus haut.

Si vous n'avez pas de test pour cela, il devient beaucoup plus difficile (plus coûteux) de changer le code, surtout si ce n'est pas vous qui le modifiez.

TDD est un moyen de vous assurer que vous avez des tests pour TOUT, mais tant que vous écrivez les tests, tout va bien. D'habitude, les écrire le même jour est utile, car vous n'allez pas le faire plus tard, n'est-ce pas? :)



Réponse aux commentaires:

une quantité décente de méthodes ne peuvent pas être testées dans un contexte particulier car elles dépendent ou dépendent d'autres méthodes

Il y a trois choses que ces méthodes peuvent appeler:

Méthodes publiques d'autres classes

Nous pouvons simuler d'autres classes afin d'y définir l'état. Nous contrôlons le contexte afin que ce ne soit pas un problème là-bas.

* Méthodes protégées ou privées sur le même *

Tout ce qui ne fait pas partie de l'API publique d'une classe n'est généralement pas testé directement.

Vous voulez tester le comportement et non l'implémentation et si une classe fait tout son travail dans une grande méthode publique ou dans plusieurs méthodes protégées plus petites appelées implémentation . Vous voulez pouvoir CHANGER ces méthodes protégées SANS toucher vos tests. Parce que vos tests vont casser si votre code change de comportement! C’est ce que vos tests sont là pour vous dire quand vous cassez quelque chose :)

Méthodes publiques sur la même classe

Cela n'arrive pas très souvent le fait? Et si cela ressemble à l'exemple suivant, il y a plusieurs façons de gérer cela:

$stuff = new Stuff();
$stuff->setBla(12);
$stuff->setFoo(14);
$stuff->execute(); 

Que les setters existent et ne font pas partie de la signature de la méthode execute est un autre sujet;)

Ce que nous pouvons tester ici, c'est si les exécutions explosent lorsque nous définissons des valeurs erronées. Cela setBlalève une exception lorsque vous passez une chaîne peut être testé séparément, mais si nous voulons tester que ces deux valeurs autorisées (12 & 14) ne fonctionnent pas ensemble (pour une raison quelconque) que celle d'un cas de test.

Si vous voulez une "bonne" suite de tests, vous pouvez, en php, peut-être (!) Ajouter une @covers Stuff::executeannotation pour vous assurer que vous ne générez une couverture de code que pour cette méthode et que les autres éléments qui ne font que configurer doivent être testés séparément vous voulez que).

Le problème est donc le suivant: vous devez peut-être d'abord créer une partie du monde environnant, mais vous devriez être capable d'écrire des scénarios de test significatifs qui ne couvrent généralement qu'une ou deux fonctions réelles (les ajusteurs ne comptent pas ici). Le reste peut être moqué à l'éther ou être testé en premier et ensuite invoqué (voir @depends)


* Remarque: La question a été migrée depuis SO et concernait initialement PHP / PHPUnit, c'est pourquoi l'exemple de code et les références proviennent du monde php. Je pense que cela s'applique également à d'autres langages, car phpunit ne diffère pas beaucoup des autres xUnit. cadres de test.

édorien
la source
Très détaillé et instructif ... Vous avez dit "Vous voulez tester des comportements, pas seulement des méthodes uniques sans contexte.", Il est certain qu'un nombre décent de méthodes ne peuvent pas être testées dans un contexte particulier car elles dépendent ou dépendent d'autres méthodes. , par conséquent, une condition de test utile ne serait contextuelle que si les personnes à charge étaient également testées? Ou est-ce que je
comprends
@ robinsonc494 Je vais éditer un exemple qui explique peut-être un peu mieux où je veux en venir
edorian
merci pour les modifications et l'exemple, cela aide certainement. Je pense que ma confusion (si vous pouvez l'appeler ainsi) était que, même si je lisais sur le test de "comportement", je pensais de manière inhérente (peut-être?) À des cas de test centrés sur la mise en œuvre.
Zcourts
@ robinsonc494 Peut-être pensez-vous de cette façon: si vous frappez quelqu'un à qui il renvoie l'éther, appelez les flics ou fuyez. Ce comportement. C'est ce que fait la personne. Le fait qu'il utilise ses moules déclenchées par de petites charges électriques de son cerveau est mis en œuvre. Si vous voulez tester la réaction de quelqu'un, vous le frappez et voyez s'il agit comme vous le souhaitiez. Vous ne le mettez pas dans un scanner cérébral pour voir si les impulsions sont envoyées aux moules. Certains vont assez bien pour les cours;)
Edorian
3
Je pense que le meilleur exemple que j'ai vu de TDD et qui m'a vraiment aidé à comprendre comment écrire les scénarios de test est le Bowling Game Kata de Oncle Bob Martin. slideshare.net/lalitkale/bowling-game-kata-by-robert-c-martin
Amy Anuszewski
2

Les tests et les tests unitaires ne sont pas la même chose. Le test unitaire est un sous-ensemble très important et intéressant du test global. Je dirais que l'objectif des tests unitaires nous fait penser à ce type de tests d'une manière qui contredit quelque peu les citations ci-dessus.

Premièrement, si nous suivons le TDD ou même le DTH (c’est-à-dire que nous développons et testons en étroite harmonie), nous utilisons les tests que nous écrivons afin de nous assurer que notre conception est correcte. En réfléchissant sur les cas critiques et en écrivant des tests en conséquence, nous évitons que les bugs ne se manifestent. Nous écrivons donc des tests que nous nous attendons à réussir (OK dès le début du TDD, ils échouent, mais ce n'est qu'un artefact de commande, le code est terminé, nous nous attendons à ce qu'il soit adopté, et la plupart le sont, car vous avez pensé au code.

Deuxièmement, les tests unitaires prennent tout leur sens lorsque nous procédons à une refactorisation. Nous modifions notre mise en œuvre, mais nous attendons que les réponses restent les mêmes. Le test unitaire constitue notre protection contre la rupture de notre contrat d'interface. Donc, encore une fois, nous attendons les tests.

Cela implique que pour notre interface publique, qui est vraisemblablement stable, nous avons besoin d'une traçabilité claire afin que nous puissions voir que chaque méthode publique est testée.

Pour répondre à votre question explicite: Les tests unitaires pour interface publique ont une valeur.

édité en réponse commentaire:

Tester des méthodes privées? Oui, nous devrions le faire, mais si nous devions ne pas tester quelque chose, nous ferions un compromis. Après tout, si les méthodes publiques fonctionnent, est-ce que ces bugs dans le domaine privé peuvent être si importants? De manière pragmatique, le désabonnement a tendance à se produire dans le domaine privé, vous travaillez dur pour maintenir votre interface publique, mais si les éléments dont vous dépendez changent, les éléments privés risquent de changer. À un moment donné, il peut s'avérer difficile de maintenir les tests internes. Cet effort est-il bien dépensé?

Djna
la source
Donc, juste pour m'assurer que je comprends ce que vous dites: lors du test, concentrez-vous sur le test de l'interface publique, n'est-ce pas? En supposant que cela soit exact… n'y a-t-il pas une plus grande possibilité de laisser des bogues dans les méthodes / interfaces privées pour lesquels vous n'avez pas fait de tests unitaires, des bogues délicats dans l'interface «privée» non testée pourraient éventuellement conduire à un test réussi aurait vraiment dû échouer. Ai-je tort de le penser?
Zcourts
En utilisant la couverture de code, vous pouvez savoir si le code de vos méthodes privées n’est pas exécuté lors du test de vos méthodes publiques. Si vos méthodes publiques sont entièrement couvertes, toutes les méthodes privées non couvertes sont évidemment inutilisées et peuvent être supprimées. Sinon, vous avez besoin de plus de tests pour vos méthodes publiques.
David Harkness
2

Les tests unitaires doivent faire partie d'une stratégie de test plus large. Je suis ces principes pour choisir quels types de tests écrire et quand:

  • Concentrez-vous sur la rédaction de tests de bout en bout. Vous couvrez plus de code par test qu'avec les tests unitaires et obtenez ainsi plus de tests pour votre argent. Faites-en la validation automatique du système dans son ensemble.

  • Passez à la rédaction de tests unitaires autour de pépites de logique compliquée. Les tests unitaires valent leur pesanteur dans les situations où il serait difficile de déboguer des tests de bout en bout ou difficiles à écrire pour obtenir une couverture de code adéquate.

  • Attendez que l'API que vous testez soit stable pour écrire l'un ou l'autre type de test. Vous voulez éviter de refactoriser à la fois votre implémentation et vos tests.

Rob Ashton a rédigé un excellent article sur ce sujet, dont je me suis largement inspiré pour énoncer les principes ci-dessus.

Edward Brey
la source
+1 - Je ne suis pas nécessairement d'accord avec tous vos points (ou avec les points de l'article), mais je suis également d'accord pour dire que la plupart des tests unitaires sont inutiles s'ils sont effectués à l'aveugle (approche TDD). Cependant, ils sont extrêmement utiles si vous décidez intelligemment de ce qui vaut la peine de passer du temps à faire des tests unitaires. Je suis tout à fait d’accord pour dire que l’écriture de tests de niveau supérieur, en particulier de tests automatisés au niveau de sous-systèmes, en vaut la chandelle. Le problème avec les tests automatisés de bout en bout est qu’ils seraient difficiles, sinon tout à fait irréalistes, pour un système ayant une taille ou une complexité quelconque.
Dunk
0

J'ai tendance à suivre une approche différente des tests unitaires qui semble bien fonctionner. Au lieu de penser à un test unitaire comme à "Tester un comportement", je le considère plutôt comme "une spécification que mon code doit suivre". De cette façon, vous pouvez fondamentalement déclarer qu'un objet doit se comporter d'une certaine manière et, étant donné que vous supposez qu'ailleurs dans votre programme, vous pouvez être presque certain qu'il est relativement exempt de bogues.

Si vous écrivez une API publique, cela est extrêmement précieux. Cependant, vous aurez toujours besoin d’une bonne dose de tests d’intégration de bout en bout, car une couverture à 100% des tests unitaires n’en vaut généralement pas la peine et ratera ce que la plupart des gens considèrent comme "non testable" par des méthodes de tests unitaires (moqueurs, etc)

Earlz
la source