Comment détecter les problèmes de dépendance avec les tests unitaires lorsque vous utilisez des objets fictifs?


98

Vous avez une classe X et vous écrivez des tests unitaires qui vérifient le comportement X1. Il y a aussi la classe A qui prend X comme dépendance.

Lorsque vous écrivez des tests unitaires pour A, vous vous moquez de X. En d'autres termes, lors du test d'unité A, vous définissez (postulez) le comportement du simulacre de X sur X1. Le temps passe, les gens utilisent votre système, les besoins changent, X évolue: vous modifiez X pour afficher le comportement X2. De toute évidence, les tests unitaires pour X échoueront et vous devrez les adapter.

Mais quoi avec A? Les tests unitaires pour A n'échoueront pas lorsque le comportement de X sera modifié (en raison de la dérision de X). Comment détecter que le résultat de A sera différent s'il est exécuté avec le "réel" (modifié) X?

J'attends des réponses du type "Ce n'est pas le but des tests unitaires", mais quelle est alors la valeur des tests unitaires? Est-ce que cela vous dit vraiment seulement que lorsque tous les tests sont réussis, vous n'avez pas introduit de changement radical? Et lorsque le comportement de certaines classes change (volontairement ou non), comment pouvez-vous détecter (de préférence de manière automatisée) toutes les conséquences? Ne devrions-nous pas nous concentrer davantage sur les tests d'intégration?



36
En plus de toutes les réponses suggérées, je dois dire que je suis en désaccord avec l'énoncé suivant: «Cela vous dit -il vraiment seulement que lorsque tous les tests sont réussis, vous n'avez pas introduit de changement radical? Si vous pensez vraiment que la suppression de la peur de la refactorisation a peu de valeur, vous êtes sur la voie de l'écriture d'un code non
maintenable

5
Les tests unitaires vous indiquent si votre unité de code se comporte comme prévu. Pas plus ou moins. Les doubles et les doubles tests fournissent un environnement artificiel et contrôlé vous permettant d’exercer votre unité de code (de manière isolée) pour voir si elle répond à vos attentes. Pas plus ou moins.
Robert Harvey

2
Je crois que votre prémisse est incorrecte. Lorsque vous mentionnez que X1vous dites que Ximplémente l'interface X1. Si vous modifiez l'interface X1par X2le modèle que vous avez utilisé dans les autres tests ne devrait plus être compilé, vous devez donc également corriger ces tests. Les changements dans le comportement de la classe ne devraient pas importer. En fait, votre classe Ane devrait pas dépendre des détails de l’implémentation (ce que vous changeriez dans ce cas). Les tests unitaires Asont donc toujours corrects et vous indiquent que cela Afonctionne si l’implémentation de l’interface est idéale.
Bakuriu

5
Je ne sais pas pour vous, mais quand je dois travailler sur une base de code sans test, j'ai très peur, je vais casser quelque chose. Et pourquoi? Parce qu'il arrive si souvent que quelque chose se brise alors que ce n'était pas prévu. Et bénissez le cœur de nos testeurs, ils ne peuvent pas tout tester. Ou même proche. Mais un test unitaire sera heureusement loin d’être ennuyeux.
CorsiKa

Réponses:


125

Lorsque vous écrivez des tests unitaires pour A, vous vous moquez de X

Le faites vous? Je ne le fais pas, sauf obligation absolue. Je dois si:

  1. X est lent ou
  2. X a des effets secondaires

Si aucune de ces conditions ne s'applique, mes tests unitaires Atesteront Xégalement. Faire autre chose, ce serait prendre des tests d'isolement à l'extrême illogique.

Si vous avez des parties de votre code qui utilisent des exemples d'autres parties de votre code, je suis d'accord: quel est l'intérêt de tels tests unitaires? Alors ne fais pas ça. Laissez ces tests utiliser les dépendances réelles car ils forment ainsi des tests beaucoup plus précieux.

Et si des personnes s’énervent en appelant ces tests, des "tests unitaires", appelez-les simplement "tests automatisés" et continuez à écrire de bons tests automatisés.


94
@ Laiv, Non, les tests unitaires sont supposés fonctionner comme une unité, c'est-à-dire exécutés isolément des autres tests . Les nœuds et les graphiques peuvent faire une randonnée. Si je peux exécuter un test de bout en bout libre, isolé et sans effets secondaires en peu de temps, c'est un test unitaire. Si vous n'aimez pas cette définition, appelez-le un test automatisé et arrêtez d'écrire des tests de merde pour vous adapter à une sémantique idiote.
David Arno

9
@DavidArno Il existe hélas une définition très large d'isolés. Certains voudraient qu'une "unité" inclue le niveau intermédiaire et la base de données. Ils peuvent croire ce qu'ils veulent, mais il y a de fortes chances que, sur un développement de toute taille, les roues ne fonctionnent plus assez rapidement, car le responsable de la construction s'en chargera. Généralement, s’ils sont isolés dans l’assemblée (ou l’équivalent), c’est très bien. NB: si vous codez pour des interfaces, il est beaucoup plus facile d'ajouter des moqueurs et des DI plus tard.
Robbie Dee

13
Vous préconisez un autre type de test plutôt que de répondre à la question. Ce qui est un point valable, mais c'est une manière assez sournoise de le faire.
Phil Frost

17
@PhilFrost, pour me citer: " Et si des gens s'énervent à l'idée d'appeler ces tests, appelez-les" tests unitaires ", appelez-les simplement" tests automatisés "et continuez à écrire de bons tests automatisés. " Écrivez des tests utiles, pas des tests idiots. qui répondent simplement à une définition aléatoire d'un mot. Ou encore, acceptez le fait que votre définition du "test unitaire" est peut-être erronée et que vous utilisez trop de simulacres, car vous vous trompez. Dans les deux cas, vous obtiendrez de meilleurs tests.
David Arno

30
Je suis avec @DavidArno sur celui-ci; ma stratégie de test a changé après avoir regardé cette conférence d'Ian Cooper: vimeo.com/68375232 . Pour résumer en une seule phrase: Ne testez pas les cours . Tester les comportements . Vos tests ne doivent pas connaître les classes / méthodes internes utilisées pour implémenter le comportement souhaité; ils ne devraient connaître que la surface publique de votre API / bibliothèque et ils devraient le tester. Si les tests ont trop de connaissances, vous testez les détails de la mise en œuvre et vos tests deviennent fragiles, couplés à votre mise en œuvre, et ne sont en réalité qu'une ancre autour du cou.
Richiban

79

Vous avez besoin des deux. Des tests unitaires pour vérifier le comportement de chacune de vos unités et quelques tests d'intégration pour vous assurer qu'ils sont connectés correctement. Le problème lié au fait de ne compter que sur les tests d'intégration est l'explosion combinatoire résultant d'interactions entre toutes vos unités.

Supposons que vous ayez la classe A, qui nécessite 10 tests unitaires pour couvrir tous les chemins. Ensuite, vous avez une autre classe B, qui nécessite également 10 tests unitaires pour couvrir tous les chemins que le code peut emprunter. Maintenant, supposons que dans votre application, vous deviez alimenter la sortie de A en B. Votre code peut maintenant suivre 100 chemins différents, de l'entrée de A à la sortie de B.

Avec les tests unitaires, vous n'avez besoin que de 20 tests unitaires + 1 test d'intégration pour couvrir tous les cas.

Avec les tests d'intégration, vous aurez besoin de 100 tests pour couvrir tous les chemins de code.

Voici une très bonne vidéo sur les inconvénients des tests d'intégration uniquement. Les tests intégrés de JB Rainsberger sont une arnaque HD


1
Je suis sûr que ce n’est pas un hasard si le problème de l’efficacité des tests d’intégration s’est accompagné de tests unitaires couvrant toutes les couches.
Robbie Dee

16
Oui, mais nulle part vos tests de 20 unités n’ont besoin de se moquer. Si vous avez vos 10 tests de A qui couvrent la totalité de A et vos 10 tests qui couvrent la totalité de B et retestez également 25% de A en bonus, cela semble être entre "bien" et une bonne chose. Se moquer des tests de A en Bs semble activement stupide (à moins qu'il y ait vraiment une raison pour que A soit un problème, c'est-à-dire que c'est la base de données ou qu'elle apporte une longue toile à d'autres)
Richard Tingle

9
Je ne suis pas d'accord avec l'idée qu'un seul test d'intégration est suffisant si vous souhaitez une couverture complète. Les réactions de B sur les sorties A varieront en fonction de la sortie; Si la modification d'un paramètre dans A modifie sa sortie, B risque de ne pas le gérer correctement.
Matthieu M.

3
@ Eternal21: Mon point était que parfois, le problème ne réside pas dans le comportement individuel, mais dans une interaction inattendue. C'est-à-dire lorsque le colle entre A et B se comporte de manière inattendue dans certains scénarios. Donc, A et B agissent tous les deux conformément aux spécifications, et le cas heureux fonctionne, mais sur certaines entrées, il y a un bug dans le code de collage ...
Matthieu M.

1
@MatthieuM. Je dirais que cela dépasse la portée des tests unitaires. Le code de collage peut être testé individuellement tandis que les interactions entre A et B via le code de collage constituent un test d'intégration. Lorsque des cas particuliers ou des bogues sont détectés, ils peuvent être ajoutés aux tests unitaires du code collé et finalement vérifiés dans les tests d'intégration.
Andrew T Finnell

72

Lorsque vous écrivez des tests unitaires pour A, vous vous moquez de X. En d'autres termes, lors du test d'unité A, vous définissez (postulez) le comportement du simulacre de X sur X1. Le temps passe, les gens utilisent votre système, les besoins changent, X évolue: vous modifiez X pour afficher le comportement X2. De toute évidence, les tests unitaires pour X échoueront et vous devrez les adapter.

Woah, attendez un moment. Les implications des tests pour l’échec de X sont trop importantes pour passer sous silence.

Si le changement d'implémentation de X de X1 à X2 interrompt les tests unitaires pour X, cela signifie que vous avez apporté une modification incompatible en arrière au contrat X.

X2 n’est pas un X, au sens de Liskov , vous devriez donc réfléchir à d’autres moyens de répondre aux besoins de vos parties prenantes (comme l’introduction d’une nouvelle spécification Y mise en œuvre par X2).

Pour des informations plus détaillées, voir Pieter Hinjens: La fin des versions de logiciels ou Rich Hickey Simple Made Easy .

Du point de vue de A, il est indispensable que le collaborateur respecte le contrat X. Et vous observez en fait que le test isolé pour A ne vous donne aucune assurance que A reconnaît les collaborateurs qui violent le contrat X.

Examiner les tests intégrés sont une arnaque ; en haut niveau, vous devez avoir autant de tests isolés que nécessaire pour vous assurer que X2 met en œuvre le contrat X correctement, et autant de tests isolés que nécessaire pour vous assurer que A fait ce qu'il convient en donnant des réponses intéressantes d'un X, et un nombre plus petit de tests intégrés pour s'assurer que X2 et A sont d'accord sur ce que signifie X.

Vous verrez parfois cette distinction exprimée en tests solitaires vs sociabletests; voir Jay Fields Travailler efficacement avec les tests unitaires .

Ne devrions-nous pas nous concentrer davantage sur les tests d'intégration?

Encore une fois, les tests intégrés sont une arnaque - Rainsberger décrit en détail une boucle de rétroaction positive qui est commune (dans ses expériences) aux projets qui reposent sur des tests intégrés (orthographe de note). En résumé, sans les tests isolés / solitaires appliquant une pression sur la conception , la qualité se dégrade, ce qui entraîne davantage d'erreurs et des tests plus intégrés ....

Vous aurez également besoin de (quelques) tests d'intégration. Outre la complexité introduite par plusieurs modules, l'exécution de ces tests a tendance à avoir plus de traînée que les tests isolés; il est plus efficace d’itérer des vérifications très rapides lorsque le travail est en cours, en enregistrant les vérifications supplémentaires lorsque vous pensez avoir terminé.


8
Cela devrait être la réponse acceptée. La question décrit une situation dans laquelle le comportement d'une classe a été modifié de manière incompatible, mais semble toujours identique à l'extérieur. Le problème réside ici dans la conception de l'application, pas dans les tests unitaires. La façon dont cette circonstance serait prise en compte dans les tests utilise un test d'intégration entre ces deux classes.
Nick Coad

1
"sans les tests isolés / solitaires appliquant une pression à la conception, la qualité se dégrade". Je pense que c'est un point important. Outre les tests de comportement, les tests unitaires ont pour effet de vous forcer à avoir une conception plus modulaire.
MickaëlG

Je suppose que tout cela est vrai, mais en quoi cela m'aide-t-il si une dépendance externe introduit une modification incompatible avec le passé dans le contrat X? Peut-être qu'une classe performante d'E / S dans une bibliothèque casse la compatibilité et nous nous moquons de X parce que nous ne voulons pas que les tests unitaires dans CI dépendent de fortes E / S. Je pense que le PO demande à tester cela, et je ne comprends pas comment cela répond à la question. Comment tester pour cela?
gerrit

15

Permettez-moi de commencer par dire que le principe de base de la question est imparfait.

Vous ne testez jamais (ni ne vous moquez) des implémentations, vous testez (et vous moquez) des interfaces .

Si j'ai une vraie classe X qui implémente l'interface X1, je peux écrire une maquette XM qui est également conforme à X1. Ensuite, ma classe A doit utiliser quelque chose qui implémente X1, qui peut être soit la classe X, soit une maquette XM.

Supposons maintenant que nous changions X pour implémenter une nouvelle interface X2. Bien évidemment, mon code ne compile plus. A requiert quelque chose qui implémente X1 et qui n'existe plus. Le problème a été identifié et peut être résolu.

Supposons qu'au lieu de remplacer X1, nous le modifions simplement. Maintenant, la classe A est définie. Cependant, le simulateur XM n'implémente plus l'interface X1. Le problème a été identifié et peut être résolu.


La base entière du test unitaire et du mocking est que vous écrivez du code utilisant des interfaces. Le consommateur d'une interface ne se soucie pas de savoir comment le code est implémenté, seulement que le même contrat soit respecté (entrées / sorties).

Cela tombe en panne lorsque vos méthodes ont des effets secondaires, mais je pense que cela peut être exclu sans risque car "ne peut pas être testé ou simulé à l'unité".


11
Cette réponse fait de nombreuses hypothèses qui ne doivent pas tenir. Premièrement, il suppose, en gros, que nous sommes en C # ou en Java (ou, plus précisément, dans un langage compilé, que le langage a des interfaces et que X implémente une interface; aucune de celles-ci ne doit être vraie). Deuxièmement, cela suppose que toute modification du comportement ou du "contrat" ​​de X nécessite une modification de l' interface (telle que comprise par le compilateur) implémentée par X. Ceci n’est manifestement pas vrai, même si nous sommes en Java ou en C #; vous pouvez modifier une implémentation de méthode sans changer sa signature.
Mark Amery

6
@MarkAmery Il est vrai que la terminologie "interface" est plus spécifique à C # ou à Java, mais je pense que le problème tient en supposant un "contrat" ​​défini de comportement (et si cela n'est pas codifié, il est impossible de le détecter automatiquement). Vous avez également parfaitement raison de dire qu'une mise en œuvre peut être modifiée sans modification du contrat. Toutefois, un changement d'implémentation sans modification d'interface (ou de contrat) ne devrait avoir d'incidence sur aucun consommateur. Si le comportement de A dépend de la manière dont l'interface (ou le contrat) est mise en œuvre, il est impossible de tester (de manière significative).
Vlad274

1
"Vous avez également parfaitement raison de dire qu'une mise en œuvre peut être modifiée sans modification du contrat" , bien que cela soit également vrai, ce n'est pas ce que j'essayais de dire. J'affirme plutôt une distinction entre le contrat (ce qu'un programmeur comprend de ce qu'un objet est censé faire, peut-être spécifié dans la documentation) et l' interface (une liste de signatures de méthodes, comprise par le compilateur), et affirmant que le contrat peut être changé sans changer l'interface. Toutes les fonctions avec la même signature sont interchangeables du point de vue du système de types, mais pas dans la réalité!
Mark Amery

4
@MarkAmery: Je ne pense pas que Vlad utilise le mot "interface" dans le même sens que vous l'utilisez; D'après ma réponse, il ne s'agit pas d'interfaces au sens étroit du langage C # / Java (c'est-à-dire d'un ensemble de signatures de méthodes), mais au sens général du mot, tel qu'utilisé par exemple dans les termes "interface de programmation d'application" ou même " interface utilisateur". [...]
Ilmari Karonen

6
@IlmariKaronen Si Vlad utilise "interface" pour signifier "contrat" ​​plutôt que dans le sens étroit du langage C # / Java, l'instruction "Maintenant, supposons que nous changions X pour implémenter une nouvelle interface X2. Bien évidemment, mon code ne compile plus. " est tout simplement faux, car vous pouvez modifier un contrat sans modifier les signatures de méthode. Mais honnêtement, je pense que le problème ici est que Vlad n’utilise pas l’un ou l’autre sens, mais le confond , ce qui explique pourquoi toute modification du contrat X1 entraînera nécessairement une erreur de compilation sans remarquer que cela est faux. .
Mark Amery

9

Répondant à vos questions:

quelle est la valeur des tests unitaires alors

Ils sont peu coûteux à écrire et à exécuter et vous obtenez un retour rapide. Si vous cassez X, vous découvrirez plus ou moins immédiatement si vous avez de bons tests. N'envisagez même pas d'écrire des tests d'intégration à moins que vous n'ayez testé toutes vos couches (oui, même dans la base de données).

Ne vous dit-il vraiment que lorsque tous les tests sont réussis, vous n'avez pas introduit de changement radical

Avoir des tests qui réussissent pourrait en dire très peu. Vous n'avez peut-être pas écrit assez de tests. Vous n'avez peut-être pas testé suffisamment de scénarios. La couverture de code peut aider ici mais ce n’est pas une solution miracle. Vous pouvez avoir des tests qui passent toujours . Le rouge est donc la première étape souvent négligée du rouge, du vert, du refactor.

Et quand le comportement de certaines classes change (volontairement ou non), comment pouvez-vous détecter (de préférence de manière automatisée) toutes les conséquences

Davantage de tests - bien que les outils deviennent de mieux en mieux. MAIS vous devriez définir le comportement de classe dans une interface (voir ci-dessous). NB: il y aura toujours une place pour les tests manuels au sommet de la pyramide de tests.

Ne devrions-nous pas nous concentrer davantage sur les tests d'intégration?

De plus en plus de tests d'intégration ne sont pas la solution non plus, ils sont coûteux à écrire, à exécuter et à maintenir. En fonction de votre configuration de build, votre responsable de build peut les exclure de toute façon, ce qui les rend dépendants du rappel du développeur (ce n'est jamais une bonne chose!).

J'ai vu des développeurs passer des heures à essayer de réparer les tests d'intégration brisés qu'ils auraient trouvés en cinq minutes s'ils disposaient de bons tests unitaires. Si vous ne le faites pas, essayez simplement d’exécuter le logiciel - c’est tout ce que vos utilisateurs finaux s’intéresseront. Inutile d'avoir des millions de tests unitaires réussis si tout le château de cartes s'effondre lorsque l'utilisateur exécute la suite complète.

Si vous voulez vous assurer que la classe A consomme la classe X de la même manière, vous devriez utiliser une interface plutôt qu'une concrétion. Ensuite, un changement radical est plus susceptible d’être repris au moment de la compilation.


9

C'est correct.

Les tests unitaires sont là pour tester la fonctionnalité isolée d'une unité, une vérification à première vue que cela fonctionne comme prévu et ne contient pas d'erreurs stupides.

Les tests unitaires ne sont pas là pour vérifier que l'application entière fonctionne.

Ce que beaucoup de gens oublient, c'est que les tests unitaires ne sont que le moyen le plus rapide et le plus sale de valider votre code. Une fois que vous savez que vos petites routines fonctionnent, vous devez également exécuter des tests d'intégration. Le test unitaire en soi n'est que légèrement meilleur que l'absence de test.

La raison pour laquelle nous avons des tests unitaires, c'est qu'ils sont censés être bon marché. Rapide à créer, exécuter et maintenir. Une fois que vous commencez à les transformer en tests d'intégration min, vous êtes dans un monde de douleur. Vous pouvez aussi bien faire un test d'intégration complet et ignorer les tests unitaires si vous voulez le faire.

maintenant, certaines personnes pensent qu'une unité n'est pas simplement une fonction dans une classe, mais toute la classe elle-même (moi-même inclus). Cependant, tout cela ne fait qu'augmenter la taille de l'unité. Vous aurez peut-être besoin de moins de tests d'intégration, mais vous en avez quand même besoin. Il est toujours impossible de vérifier que votre programme fait ce qu'il est supposé faire sans une suite de tests d'intégration complète.

et ensuite, vous devrez toujours exécuter les tests d'intégration complets sur un système actif (ou semi-actif) pour vérifier qu'il fonctionne avec les conditions utilisées par le client.


2

Les tests unitaires ne prouvent pas l'exactitude de quoi que ce soit. Ceci est vrai pour tous les tests. Habituellement, les tests unitaires sont combinés à une conception basée sur contrat (conception par contrat est un autre moyen de le dire) et éventuellement à des épreuves automatisées de correction, si celle-ci doit être vérifiée régulièrement.

Si vous avez des contrats réels, comprenant des invariants de classe, des conditions préalables et des conditions de publication, il est possible de prouver l'exactitude hiérarchiquement, en basant l'exactitude des composants de niveau supérieur sur les contrats des composants de niveau inférieur. C'est le concept fondamental de la conception par contrat.


Les tests unitaires ne prouvent pas l'exactitude de quoi que ce soit . Vous n'êtes pas sûr de comprendre cela, les tests unitaires vérifient leurs propres résultats? Ou avez-vous voulu dire qu'un comportement ne peut pas être prouvé correct puisqu'il peut englober plusieurs couches?
Robbie Dee

7
@RobbieDee Je suppose, il voulait dire que lorsque vous effectuez un test fac(5) == 120, vous n'avez pas prouvé qu'il fac()rendait effectivement la factorielle de son argument. Vous avez seulement prouvé que vous fac()restituez la factorielle de cinq à votre passage 5. Et même cela n’est pas certain, car fac()il est concevable de revenir 42les premiers lundis après une éclipse totale à Tombouctou ... Le problème est qu’il est impossible de prouver la conformité en vérifiant les entrées de test individuelles, vous devez vérifier toutes les entrées possibles, et aussi prouver que vous n’avez rien oublié (comme lire l’horloge système).
cmaster

1
Les tests @RobbieDee (y compris les tests unitaires) sont un substitut médiocre, souvent le meilleur disponible, au véritable objectif, à savoir une épreuve vérifiée par une machine. Examinez l’ensemble de l’espace d’état des unités testées, y compris l’espace d’état des composants ou des imitations qui s’y trouvent. À moins que vous n'ayez un espace d'état très restreint, les tests ne peuvent pas couvrir cet espace d'état. Une couverture complète serait une preuve, mais cette option n'est disponible que pour les espaces d'état minuscules, par exemple, le test d'un seul objet contenant un seul octet modifiable ou un entier de 16 bits. Les preuves automatisées sont beaucoup plus précieuses.
Frank Hileman

1
@ cmaster Vous avez très bien résumé la différence entre un test et une preuve. Merci!
Frank Hileman

2

Je trouve les tests lourdement ridicules rarement utiles. La plupart du temps, je finis par réimplémenter le comportement que la classe d'origine a déjà, ce qui contrecarre totalement le but de se moquer.

Une meilleure stratégie consiste à bien séparer les problèmes (par exemple, vous pouvez tester la partie A de votre application sans importer les parties B à Z). Une telle bonne architecture aide vraiment à écrire un bon test.

De plus, je suis tout à fait disposé à accepter les effets secondaires tant que je peux les restaurer, par exemple, si ma méthode modifie les données de la base de données, laissez-les! Tant que je peux restaurer la base de données à son état précédent, quel est le problème? De plus, mon test permet de vérifier si les données sont conformes aux attentes. Les bases de données en mémoire ou les versions de test spécifiques de dbs sont vraiment utiles (par exemple, la version de test en mémoire de RavenDB).

Enfin, j'aime bien me moquer des limites du service, par exemple, ne faites pas cet appel http au service b, mais interceptons-le et introduisons une méthode appropriée.


1

J'aimerais que les gens des deux camps comprennent que les tests de classe et de comportement ne sont pas orthogonaux.

Les tests de classe et les tests unitaires sont utilisés de manière interchangeable et ne devraient peut-être pas l'être. Certains tests unitaires viennent juste d’être implémentés dans des classes. C'est tout. Les tests unitaires sont pratiqués depuis des décennies dans des langues sans classes.

En ce qui concerne les comportements de test, il est parfaitement possible de le faire au sein de tests de classe en utilisant la construction GWT.

En outre, le fait que vos tests automatisés se déroulent en classe ou en comportement dépend plutôt de vos priorités. Certains auront besoin de prototyper rapidement et de sortir quelque chose tandis que d'autres auront des contraintes de couverture dues aux styles internes. De nombreuses raisons. Ce sont deux approches parfaitement valables. Vous payez votre argent, vous prenez votre choix.

Alors, que faire en cas de rupture de code? Si elle a été codée sur une interface, seule la concrétion doit changer (ainsi que tous les tests).

Cependant, l'introduction d'un nouveau comportement ne doit pas compromettre le système du tout. Linux et al sont pleins de fonctionnalités obsolètes. Et des choses telles que les constructeurs (et les méthodes) peuvent heureusement être surchargées sans obliger tout le code appelant à changer.

Lorsque les tests de classe gagnent, vous devez modifier une classe qui n'a pas encore été configurée (en raison de contraintes de temps, de complexité ou autre). Il est tellement plus facile de commencer avec une classe si elle comporte des tests complets.


Où vous avez concrétisé j'aurais écrit la mise en œuvre . S'agit-il d'un calque d'une autre langue (j'imagine le français) ou existe-t-il une distinction importante entre concrétion et mise en œuvre ?
Peter Taylor

0

Sauf si l'interface pour X a changé, vous n'avez pas besoin de modifier le test unitaire pour A car rien de ce qui a été associé à A n'a été modifié. On dirait que vous avez vraiment écrit un test unitaire de X et A ensemble, mais que vous l'appeliez un test unitaire de A:

Lorsque vous écrivez des tests unitaires pour A, vous vous moquez de X. En d'autres termes, lors du test d'unité A, vous définissez (postulez) le comportement du simulacre de X sur X1.

Idéalement, la maquette de X devrait simuler tous les comportements possibles de X, et pas seulement le comportement que vous attendez de X. Ainsi, peu importe ce que vous implémentez réellement dans X, A devrait déjà être capable de le gérer. Donc, aucune modification apportée à X, autre que la modification de l'interface elle-même, n'aura d'effet sur le test unitaire pour A.

Par exemple: supposons que A soit un algorithme de tri et que A fournisse les données à trier. La maquette de X doit fournir une valeur de retour NULL, une liste de données vide, un seul élément, plusieurs éléments déjà triés, plusieurs éléments non déjà triés, plusieurs éléments triés en arrière, des listes avec le même élément répété, des valeurs nulles entremêlées, ridiculement grand nombre d'éléments, et il devrait également jeter une exception.

Alors peut-être que X a initialement renvoyé des données triées le lundi et des listes vides le mardi. Mais maintenant, quand X renvoie des données non triées le lundi et lève des exceptions le mardi, A s'en fiche, ces scénarios étaient déjà couverts dans le test unitaire de A.


Que se passe-t-il si X vient de renommer l'index dans un tableau retourné de foobar à foobarRandomNumber, comment puis-je compter avec cela? si vous comprenez ce que je veux dire, c’est essentiellement mon problème, j’ai renommé une colonne renvoyée de secondName en nom de famille, tâche classique, mais mon test ne le saura jamais, car il s’est moqué de lui. J'ai juste un sentiment étrange, comme si beaucoup de personnes dans cette question n'avaient jamais réellement essayé quelque chose comme ça, avant de commenter
FantomX1

Le compilateur aurait dû détecter cette modification et vous donner une erreur de compilation. Si vous utilisez quelque chose comme JavaScript, je vous recommande de passer à TypeScript ou d'utiliser un compilateur comme Babel, capable de détecter ce type de choses.
Moby Disk

Que se passe-t-il si j'utilise des tableaux en PHP, ou en Java ou en Javascript, si vous modifiez ou supprimez un index de tableau, aucun de ces langages ne vous le dira, l'index pourrait être imbriqué au 36e - nombre pensé niveau imbriqué du tableau, donc je pense que le compilateur n'est pas la solution pour cela.
FantomX1

-2

Vous devez regarder différents tests.

Les tests unitaires eux-mêmes ne feront que tester X. Ils sont là pour vous empêcher de modifier le comportement de X mais ne pas sécuriser l'ensemble du système. Ils veillent à ce que vous puissiez refactoriser votre classe sans introduire de changement de comportement. Et si vous cassez X, vous le cassez ...

A devrait en effet simuler X pour ses tests unitaires et le test avec le simulateur devrait continuer à passer même après l'avoir modifié.

Mais il y a plus d'un niveau de test! Il existe également des tests d'intégration. Ces tests sont là pour valider l'interaction entre les classes. Ces tests ont généralement un prix plus élevé puisqu'ils n'utilisent pas de simulacres pour tout. Par exemple, un test d'intégration pourrait en réalité écrire un enregistrement dans une base de données, et un test unitaire ne devrait avoir aucune dépendance externe.

De plus, si X doit adopter un nouveau comportement, il serait préférable de fournir une nouvelle méthode permettant d'obtenir le résultat souhaité.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.