Lorsque vous effectuez des tests unitaires de la manière "appropriée", c.-à-d. Écrasez tous les appels publics et renvoyez des valeurs prédéfinies ou des simulacres, je sens que je ne teste rien en réalité. Je regarde littéralement mon code et crée des exemples basés sur le flux de logique via mes méthodes publiques. Et chaque fois que la mise en œuvre change, je dois changer ces tests, encore une fois, sans vraiment avoir le sentiment d’accomplir quelque chose d’utile (que ce soit à moyen ou à long terme). Je fais aussi des tests d'intégration (y compris les chemins non heureux) et les temps de test plus longs ne me dérangent pas. Avec ceux-ci, j'ai l'impression de tester des régressions, car ils en ont capturé plusieurs, alors que tous les tests unitaires me montrent que la mise en œuvre de ma méthode publique a changé, ce que je connais déjà.
Les tests unitaires sont un sujet vaste et j'ai l'impression que c'est moi qui ne comprends pas quelque chose ici. Quel est l'avantage décisif des tests unitaires par rapport aux tests d'intégration (hors temps)?
la source
Réponses:
Cela ressemble à la méthode que vous testez a besoin de plusieurs autres instances de classe (que vous devez simuler) et appelle plusieurs méthodes à elle seule.
Ce type de code est en effet difficile à tester, pour les raisons que vous avez décrites.
Ce que j’ai trouvé utile est de scinder ces cours en:
Ensuite, les classes de 1. sont faciles à tester, car elles acceptent simplement des valeurs et renvoient un résultat. Dans des cas plus complexes, ces classes devront peut-être effectuer elles-mêmes des appels, mais elles n'appelleront que les classes à partir de 2. (et non directement, par exemple, une fonction de base de données). Les classes à partir de 2. sont faciles à simuler (car exposer les parties du système emballé dont vous avez besoin).
Les classes de 2. et 3. ne peuvent généralement pas être testées de manière significative (car elles ne font rien d’utile par elles-mêmes, elles ne sont que du code "collé"). OTOH, ces classes ont tendance à être relativement simples (et peu), elles devraient donc être adéquatement couvertes par des tests d'intégration.
Un exemple
Une classe
Supposons que vous ayez une classe qui récupère un prix dans une base de données, applique des remises et met à jour la base de données.
Si vous avez tout cela dans la même classe, vous devrez appeler des fonctions de base de données difficiles à imiter. En pseudocode:
Les trois étapes nécessiteront un accès à la base de données, donc beaucoup de simulations (complexes), qui risquent de se rompre si le code ou la structure de la base de données change.
Séparer
Vous vous divisez en trois classes: PriceCalculation, PriceRepository, App.
PriceCalculation effectue uniquement le calcul réel et fournit les valeurs dont il a besoin. L'application relie tout:
De cette façon:
Enfin, il se peut que PriceCalculation doive faire ses propres appels de base de données. Par exemple, seul PriceCalculation connaissant les données dont il a besoin, il ne peut donc pas être récupéré à l'avance par App. Ensuite, vous pouvez lui transmettre une instance de PriceRepository (ou une autre classe de référentiel), personnalisée selon les besoins de PriceCalculation. Il faudra ensuite se moquer de cette classe, mais ce sera simple, car l'interface de PriceRepository est simple, par exemple
PriceRepository.getPrice(articleNo, contractType)
. Plus important encore, l'interface de PriceRepository isole PriceCalculation de la base de données. Par conséquent, les modifications apportées au schéma de la base de données ou à l'organisation des données ne risquent pas de modifier son interface et, partant, de casser les simulacres.la source
C'est une fausse dichotomie.
Les tests unitaires et les tests d'intégration ont deux objectifs similaires, mais différents. Le but des tests unitaires est de vous assurer que vos méthodes fonctionnent. Concrètement, les tests unitaires permettent de s'assurer que le code respecte le contrat défini par les tests unitaires. Cela est évident dans la conception des tests unitaires: ils indiquent spécifiquement ce que le code est censé faire et affirment que le code le fait.
Les tests d'intégration sont différents. Les tests d'intégration exercent l'interaction entre les composants logiciels. Vous pouvez avoir des composants logiciels qui passent tous leurs tests et qui échouent tout de même les tests d'intégration car ils n'interagissent pas correctement.
Toutefois, si les tests unitaires présentent un avantage décisif, c’est que les tests unitaires sont beaucoup plus faciles à configurer et demandent beaucoup moins de temps et d’efforts que les tests d’intégration. Lorsqu'ils sont utilisés correctement, les tests unitaires encouragent le développement de code "testable", ce qui signifie que le résultat final sera plus fiable, plus facile à comprendre et à maintenir. Le code testable a certaines caractéristiques, comme une API cohérente, un comportement répétable et il renvoie des résultats faciles à affirmer.
Les tests d'intégration sont plus difficiles et plus coûteux, car vous avez souvent besoin de moqueries élaborées, d'une configuration complexe et d'assertions difficiles. Au plus haut niveau d’intégration système, imaginez-vous essayer de simuler une interaction humaine dans une interface utilisateur. Des systèmes logiciels entiers sont consacrés à ce type d’automatisation. Et c’est l’automatisation que nous recherchons; les tests humains ne sont pas reproductibles et ne se développent pas comme les tests automatisés.
Enfin, les tests d'intégration ne garantissent pas la couverture du code. Combien de combinaisons de boucles de code, de conditions et de branches testez-vous avec vos tests d'intégration? Tu sais vraiment? Il existe des outils que vous pouvez utiliser avec les tests unitaires et les méthodes testées pour vous indiquer le niveau de couverture de code dont vous disposez et la complexité cyclomatique de votre code. Mais ils ne fonctionnent vraiment bien qu'au niveau de la méthode, où vivent les tests unitaires.
Si vos tests changent chaque fois que vous effectuez une refactorisation, le problème est différent. Les tests unitaires sont censés consister à documenter ce que fait votre logiciel, à prouver qu'il le fait, puis à prouver qu'il le fait à nouveau lorsque vous restructurez l'implémentation sous-jacente. Si votre API change ou si vous avez besoin que vos méthodes changent en fonction d'une modification de la conception du système, c'est ce qui est censé se produire. Si cela se produit souvent, pensez à écrire vos tests avant d'écrire du code. Cela vous obligera à réfléchir à l'architecture globale et vous permettra d'écrire du code avec l'API déjà établie.
Si vous passez beaucoup de temps à écrire des tests unitaires pour du code trivial comme
alors vous devriez réexaminer votre approche. Le test unitaire est censé tester le comportement, et il n'y a pas de comportement dans la ligne de code ci-dessus. Vous avez cependant créé une dépendance dans votre code quelque part, car cette propriété sera certainement référée ailleurs dans votre code. Au lieu de cela, pensez à écrire des méthodes qui acceptent la propriété nécessaire en tant que paramètre:
Désormais, votre méthode ne dépend d'aucun élément en dehors d'elle-même et est maintenant plus testable, car elle est complètement autonome. Certes, vous ne pourrez pas toujours faire cela, mais cela déplace votre code dans le sens où il est plus testable, et cette fois, vous écrivez un test unitaire du comportement réel.
la source
Les tests unitaires avec mock doivent s’assurer que l’implémentation de la classe est correcte. Vous simulez les interfaces publiques des dépendances du code que vous testez. De cette façon, vous avez le contrôle sur tout ce qui est externe à la classe et vous êtes certain qu'un test qui échoue est dû à quelque chose de interne à la classe et non à l'un des autres objets.
Vous testez également le comportement de la classe sous test et non la mise en œuvre. Si vous refactorisez le code (en créant de nouvelles méthodes internes, etc.), les tests unitaires ne doivent pas échouer. Mais si vous modifiez ce que fait la méthode publique, les tests doivent absolument échouer car vous avez modifié le comportement.
On dirait aussi que vous écrivez les tests après avoir écrit le code, essayez plutôt d'écrire les premiers tests. Essayez de décrire le comportement que la classe devrait avoir, puis écrivez la quantité minimale de code nécessaire à la réussite des tests.
Les tests unitaires et les tests d'intégration sont utiles pour garantir la qualité de votre code. Les tests unitaires examinent chaque composant séparément. Et les tests d'intégration garantissent que tous les composants interagissent correctement. Je veux avoir les deux types dans ma suite de tests.
Les tests unitaires m'ont aidé dans mon développement car je peux me concentrer sur une partie de l'application à la fois. Se moquant des composants que je n'ai pas encore fabriqués. Ils sont également intéressants pour la régression, car ils documentent tous les bugs de la logique que j'ai trouvée (même dans les tests unitaires).
MISE À JOUR
La création d'un test qui s'assure uniquement que les méthodes sont appelées a une valeur en ce sens que vous vous assurez que les méthodes sont effectivement appelées. Si vous écrivez d'abord vos tests, vous disposez notamment d'une liste de contrôle des méthodes à utiliser. Comme ce code est assez procédural, vous n’avez pas grand chose à vérifier si ce n’est que les méthodes sont appelées. Vous protégez le code pour le changement dans le futur. Lorsque vous devez appeler une méthode avant l'autre. Ou qu'une méthode soit toujours appelée même si la méthode initiale lève une exception.
Le test de cette méthode peut ne jamais changer ou ne peut changer que lorsque vous modifiez les méthodes. Pourquoi est-ce une mauvaise chose? Cela aide à renforcer l'utilisation des tests. Si vous devez corriger un test après avoir changé le code, vous aurez l'habitude de changer les tests avec le code.
la source
Je rencontrais une question similaire - jusqu'à ce que je découvre la puissance des tests de composants. En bref, ils sont identiques aux tests unitaires, sauf que vous ne vous moquez pas par défaut, mais utilisez des objets réels (idéalement via une injection de dépendance).
De cette façon, vous pouvez créer rapidement des tests robustes avec une bonne couverture de code. Pas besoin de mettre à jour vos mocks tout le temps. Il est peut-être un peu moins précis que les tests unitaires avec 100% de simulacres, mais le temps et l’argent économisés compensent ce manque. La seule chose dont vous avez vraiment besoin pour utiliser des simulacres ou des agencements est un système de stockage ou des services externes.
En fait, les moqueries excessives sont un anti-modèle: les anti-modèles et les moqueurs de TDD sont diaboliques .
la source
Bien que l'opération ait déjà marqué une réponse, j'ajoute simplement mes 2 centimes ici.
Et aussi en réponse à
Il y a un utile mais pas exactement ce que l'OP a demandé:
Les tests unitaires fonctionnent, mais il y a encore des bugs?
D'après ma petite expérience des suites de tests, je comprends que les tests unitaires consistent toujours à tester les fonctionnalités les plus élémentaires d'une classe en termes de méthodes. À mon avis, chaque méthode, qu'elle soit publique, privée ou interne, mérite d'avoir des tests unitaires dédiés. Même dans mon expérience récente, j'avais une méthode publique qui appelait une autre petite méthode privée. Donc, il y avait deux approches:
Si vous pensez logiquement, le point d’avoir une méthode privée est: la méthode publique principale devient trop volumineuse ou malpropre. Afin de résoudre ce problème, vous reformulez judicieusement et créez de petits morceaux de code qui méritent d'être des méthodes privées distinctes, ce qui rend ensuite votre méthode publique principale moins volumineuse. Vous refactorisez en gardant à l'esprit que cette méthode privée pourrait être réutilisée ultérieurement. Il peut y avoir des cas dans lesquels il n'y a pas d'autre méthode publique dépendant de cette méthode privée, mais qui sait à propos de l'avenir.
Considérant le cas où la méthode privée est réutilisée par beaucoup d'autres méthodes publiques.
Donc, si j'avais choisi l'approche 1: j'aurais dupliqué les tests unitaires et ils auraient été compliqués, car vous disposiez d'un nombre de tests unitaires pour chaque branche de la méthode publique ainsi que de la méthode privée.
Si j'avais choisi l'approche 2: le code écrit pour les tests unitaires serait relativement moins, et il serait beaucoup plus facile à tester.
Prise en compte du cas où la méthode privée n'est pas réutilisée Il est inutile d'écrire un test unitaire séparé pour cette méthode.
En ce qui concerne les tests d'intégration , ils ont tendance à être exhaustifs et de plus haut niveau. Ils vous diront que si vous donnez votre avis, toutes vos classes doivent en arriver à cette conclusion finale. Pour en savoir plus sur l'utilité des tests d'intégration, veuillez consulter le lien mentionné.
la source