Ce que l'on entend par «unité» dans les tests unitaires

9

Si je comprends bien en théorie sous "unité", les gens veulent dire méthode (en POO). Mais dans la pratique, les tests qui vérifient une méthode isolément sont des tests de comportement très fragiles (vérifiant non pas le résultat mais le fait qu'une certaine méthode de dépendance a été appelée). Je vois donc beaucoup de gens qui, à l'unité, comprennent un petit ensemble de classes étroitement liées. Dans ce cas, seules les dépendances externes sont moquées / tronquées et pour les dépendances qui sont à l'intérieur de l'unité, des implémentations réelles sont utilisées. Dans ce cas, il y a plus de tests d'état, significatifs (selon les spécifications) et moins fragiles. La question est donc de savoir comment vous sentez-vous face à ces approches et est-il valable d'appeler le test d'unité de la deuxième approche ou peut-être s'agit-il d'une sorte de test d'intégration de bas niveau?

Si vous voyez des considérations spécifiques sur l'application de TDD par l'une de ces méthodes de test, je vous serais reconnaissant de vos réflexions.

SiberianGuy
la source

Réponses:

11

À l'origine, TDD provenait du mouvement agile, où les tests étaient écrits à l'avance pour s'assurer que ce que vous codiez restait correct compte tenu des spécifications qui étaient désormais bien définies dans le code de test. Il est également apparu comme un aspect très important de la refactorisation, en ce sens que lorsque vous avez modifié votre code, vous pouvez vous fier aux tests pour prouver que vous n'avez pas changé le comportement du code.

Ensuite, les outils sont venus et ont pensé qu'ils connaissaient des informations sur votre code et pouvaient ensuite générer des talons de test pour vous aider à écrire vos tests unitaires, et je pense que c'est là que tout s'est mal passé.

Les talons de test sont générés par un ordinateur qui n'a aucune idée de ce que vous faites, il produit simplement un talon pour chaque méthode parce que c'est ce qu'on lui dit de faire. Cela signifie que vous disposez d'un cas de test pour chaque méthode, quelle que soit la complexité de cette méthode ou si elle convient pour les tests de manière isolée.

Cela vient des tests du mauvais côté de la méthodologie TDD. Dans TDD, vous êtes censé comprendre ce que le code doit faire, puis produire du code qui y parvient. C'est auto-réalisateur dans la mesure où vous finissez par écrire des tests qui prouvent que le code fait ce que fait le code, pas ce qu'il est censé faire. Combiné à la génération automatique de talons de test basés sur des méthodes, vous perdez à peu près votre temps à prouver chaque petit aspect de votre code qui peut si facilement se tromper lorsque tous les petits morceaux sont assemblés.

Lorsque Fowler a décrit les tests dans son livre, il a fait référence au test de chaque classe avec sa propre méthode principale. Il a amélioré cela, mais le concept est toujours le même - vous testez la classe entière pour qu'elle fonctionne dans son ensemble, tous vos tests sont regroupés pour prouver l'interaction de toutes ces méthodes afin que la classe puisse être réutilisée avec des attentes définies.

Je pense que les boîtes à outils de test nous ont rendu un mauvais service, nous ont conduit à penser que la boîte à outils est le seul moyen de faire les choses alors qu'en réalité, vous devez réfléchir davantage pour vous-même afin d'obtenir le meilleur résultat de votre code. Mettre aveuglément du code de test dans des talons de test pour de petits morceaux signifie simplement que vous devez répéter votre travail dans un test d'intégration de toute façon (et si vous voulez le faire, pourquoi ne pas sauter complètement l'étape de test unitaire désormais redondante). Cela signifie également que les gens perdent beaucoup de temps à essayer d'obtenir une couverture de test à 100% et beaucoup de temps à créer de grandes quantités de code et de données moqueuses qui auraient été mieux utilisées pour rendre le code plus facile à tester d'intégration (c'est-à-dire si vous en avez autant dépendances des données, le test unitaire n'est peut-être pas la meilleure option)

Enfin, la fragilité des tests unitaires basés sur des méthodes ne fait que montrer le problème. Le refactoring est conçu pour être utilisé avec des tests unitaires, si vos tests se cassent tout le temps parce que vous refactorez alors quelque chose a sérieusement mal tourné avec toute l'approche. La refactorisation aime créer et supprimer des méthodes, donc évidemment l'approche de test basée sur une méthode aveugle n'est pas ce qui était initialement prévu.

Je ne doute pas que de nombreuses méthodes obtiendront des tests écrits pour elles, toutes les méthodes publiques d'une classe doivent être testées, mais vous ne pouvez pas vous éloigner du concept de les tester ensemble dans le cadre d'un cas de test unique. Par exemple, si j'ai un ensemble et une méthode get, je peux écrire des tests qui mettent les données et vérifier que les membres internes sont bien définis, ou je peux utiliser chacun pour mettre des données et les extraire à nouveau pour voir si c'est toujours le même et non brouillé. Il s'agit de tester la classe, et non chaque méthode isolément. Si le setter s'appuie sur une méthode privée d'aide, alors c'est très bien - vous n'avez pas besoin de vous moquer de la méthode privée pour vous assurer que le setter fonctionne, pas si vous testez la classe entière.

Je pense que la religion aborde ce sujet, d'où vous voyez le schisme dans ce qui est maintenant connu sous le nom de développement `` axé sur le comportement '' et `` piloté par les tests '' - le concept original des tests unitaires était pour le développement axé sur le comportement.

gbjbaanb
la source
10

Une unité est le plus souvent définie comme " la plus petite partie testable d'une application ". Le plus souvent, oui, cela signifie une méthode. Et oui, cela signifie que vous ne devez pas tester le résultat d'une méthode dépendante, mais simplement que la méthode est appelée (et ensuite une seule fois, si possible, pas dans chaque test pour cette méthode).

Vous appelez cela fragile. Je pense que c'est incorrect. Les tests fragiles sont ceux qui cassent sous le moindre changement de code non lié. Autrement dit, ceux qui s'appuient sur du code qui n'est pas pertinent pour la fonctionnalité testée.

Cependant, ce que je pense que vous voulez vraiment dire, c'est que tester une méthode sans aucune de ses dépendances n'est pas approfondi. Sur ce point, je suis d'accord. Vous avez également besoin de tests d'intégration pour vous assurer que les unités de code sont correctement connectées pour créer une application.

C'est exactement le problème que le développement axé sur le comportement , et en particulier le développement piloté par les tests d'acceptation , se propose de résoudre. Mais cela ne supprime pas la nécessité de tests unitaires / développement piloté par les tests; il ne fait que le compléter.

pdr
la source
Je voulais vraiment dire que les tests de comportement sont fragiles. Ils deviennent souvent faux négatifs lors du changement de base de code. Cela se produit moins avec les tests d'état (mais les tests d'état sont très rarement pour les tests unitaires)
SiberianGuy
@Idsa: Je suis un peu perdu par vos définitions. Les tests de comportement sont des tests d'intégration, testant un comportement tel que spécifié. En lisant votre question d'origine, il semble que lorsque vous dites tests d'état, vous voulez dire la même chose.
pdr
par état, je veux dire test qui vérifie l'état, résultat d'une fonction; par comportement, je veux dire test qui vérifie non pas le résultat, mais le fait qu'une fonction a été appelée
SiberianGuy
@Idsa: Dans ce cas, je suis complètement en désaccord. Ce que vous appelez test d'état, j'appelle intégration. Ce que vous appelez le comportement, j'appelle l'unité. Les tests d'intégration par leur définition même sont plus fragiles. Google "unité de test d'intégration fragile" et vous verrez que je ne suis pas seul.
pdr
il existe un journal d'articles sur les tests, mais lesquels partagent votre opinion?
SiberianGuy
2

Comme son nom l'indique, vous testez un sujet atomique dans chaque test. Un tel sujet est généralement une méthode unique. Plusieurs tests peuvent tester la même méthode, afin de couvrir le chemin heureux, les erreurs possibles, etc. Vous testez le comportement, pas la mécanique interne. Le test unitaire consiste donc vraiment à tester l'interface publique d'une classe, c'est-à-dire une méthode spécifique.

Dans les tests unitaires, une méthode doit être testée de manière isolée, c'est-à-dire en stubbing / mocking / fake any dependencies. Sinon, tester une unité avec des dépendances «réelles» en fait un test d'intégration. Il y a un temps et un lieu pour les deux types de tests. Les tests unitaires garantissent qu'un seul sujet fonctionne comme prévu, de manière autonome. Les tests d'intégration garantissent que les «vrais» sujets fonctionnent correctement ensemble.

Grant Palin
la source
1
pas tout à fait, une unité est un sujet isolé, simplement parce que l'outillage automatisé préfère traiter une méthode car cela ne la rend pas ainsi ni la meilleure. «Isolé» est la clé ici. Même si vous testez des méthodes, vous devez également tester les méthodes privées.
gbjbaanb
1

Ma règle d'or: la plus petite unité de code qui est encore suffisamment complexe pour contenir des bogues.

Qu'il s'agisse d'une méthode ou d'une classe ou d'un sous-système dépend du code particulier, aucune règle générale ne peut être donnée.

Par exemple, il ne fournit aucune valeur pour tester des méthodes getter / setter simples isolément ou des méthodes wrapper qui appellent uniquement une autre méthode. Même une classe entière peut être trop simple à tester, si la classe n'est qu'une enveloppe mince ou un adaptateur. Si la seule chose à tester est si une méthode sur un mock est appelée, alors le code testé est à éclaircir.

Dans d'autres cas, une seule méthode peut effectuer des calculs complexes qui sont précieux à tester de manière isolée.

Dans de nombreux cas, les parties complexes ne sont pas des classes individuelles mais plutôt l'intégration entre les classes. Vous testez donc deux classes ou plus à la fois. Certains diront que ce ne sont pas des tests unitaires mais des tests d'intégration, mais peu importe la terminologie: vous devez tester la complexité et ces tests devraient faire partie de la suite de tests.

JacquesB
la source