Adoption d’un test unitaire

Nous avons essayé d’introduire des tests unitaires dans notre projet actuel, mais cela ne semble pas fonctionner. Le code supplémentaire semble être devenu un casse-tête de maintenance, car lorsque notre framework interne change, nous devons corriger tous les tests unitaires qui y sont liés.

Nous avons une classe de base abstraite pour les tests unitaires de nos contrôleurs, qui agit comme un modèle appelant les implémentations de méthodes abstraites des classes enfants, c.-à-d.

J’étais un défenseur des tests unitaires, mais cela ne semble pas fonctionner sur notre projet actuel.

Quelqu’un peut-il aider à identifier le problème et comment nous pouvons faire fonctionner les tests unitaires pour nous plutôt que contre nous?

Conseils:

Évitez d’écrire du code de procédure

Les tests peuvent être difficiles à maintenir s’ils sont écrits sur un code de type procédural qui repose fortement sur l’état global ou qui se trouve profondément dans le corps d’une méthode laide. Si vous écrivez du code dans un langage OO, utilisez les constructions OO efficacement pour réduire cela.

  • Évitez l’état global si possible.
  • Évitez les statiques car elles ont tendance à se répercuter dans votre base de code et à provoquer une statique qui ne devrait pas l’être. Ils gonflent également votre contexte de test (voir ci-dessous).
  • Exploiter efficacement le polymorphism pour éviter les indicateurs et les indicateurs excessifs

Trouvez ce qui change, encapsulez-le et séparez-le de ce qui rest le même.

Il y a des points d’étranglement dans le code qui changent beaucoup plus fréquemment que les autres pièces. Faites cela dans votre base de code et vos tests deviendront plus sains.

  • Une bonne encapsulation conduit à de bonnes conceptions faiblement couplées.
  • Refactor et modulariser.
  • Gardez les tests petits et concentrés.

Plus le contexte entourant un test est large, plus il sera difficile à maintenir.

Faites tout ce que vous pouvez pour réduire les tests et le contexte dans lequel ils sont exécutés.

  • Utilisez le refactoring de méthodes composées pour tester des blocs de code plus petits.
  • Utilisez-vous un nouveau framework de test comme TestNG ou JUnit4? Ils vous permettent de supprimer les doublons dans les tests en vous proposant des crochets plus précis dans le cycle de vie du test.
  • Étudiez en utilisant des doubles de test (simulateurs, faux, stubs) pour réduire la taille du contexte de test.
  • Recherchez le modèle de générateur de données de test .

Supprimez la duplication des tests, mais assurez-vous qu’ils conservent le focus.

Vous ne pourrez probablement pas supprimer toutes les duplications, mais essayez tout de même de les supprimer là où elles causent de la douleur. Assurez-vous de ne pas supprimer autant de doublons que quelqu’un ne peut pas entrer et dire ce que le test fait en un coup d’œil. (Voir l’article de Paul Wheaton intitulé “Evil Unit Tests” pour une explication alternative du même concept.)

  • Personne ne voudra réparer un test s’il ne sait pas ce qu’il fait.
  • Suivez l’arrangement, act, pattern assert.
  • Utilisez une seule assertion par test.

Testez au bon niveau ce que vous essayez de vérifier.

Pensez à la complexité d’un test Selenium en enregistrement et en lecture et à ce qui pourrait changer sous vos yeux plutôt que de tester une méthode unique.

  • Isoler les dépendances les unes des autres.
  • Utilisez l’dependency injection / l’inversion du contrôle.
  • Utilisez des doubles de test pour initialiser un object à tester et assurez-vous de tester des unités de code isolées.
  • Assurez-vous d’écrire des tests pertinents
    • “Spring the Trap” en introduisant un bug express et assurez-vous qu’il est attrapé par un test.
  • Voir aussi: Les tests d’intégration sont une arnaque

Savoir quand utiliser les tests basés sur l’état vs basés sur les interactions

Les vrais tests unitaires nécessitent une véritable isolation. Les tests unitaires ne touchent pas une firebase database ou des sockets ouverts. Arrêtez de vous moquer de ces interactions. Vérifiez que vous parlez correctement à vos collaborateurs, et non que le résultat correct de cet appel de méthode était “42”.

Démontrer le code de conduite d’essai

C’est à débattre pour savoir si une équipe donnée va ou non tester tous les codes, ou écrire “tests en premier” pour chaque ligne de code. Mais devraient-ils d’abord écrire au moins quelques tests? Absolument. Il existe des scénarios dans lesquels le test-first est sans aucun doute le meilleur moyen d’aborder un problème.

  • Essayez cet exercice: TDD comme si vous le pensiez (Another Description)
  • Voir aussi: Développement piloté par les tests et méthode scientifique

Ressources:

  • Testé par Lasse Koskela
  • Logiciel OO croissant, guidé par des tests de Steve Freeman et Nat Pryce
  • Travailler efficacement avec Legacy Code par Michael Feathers
  • Spécification par exemple par Gojko Adzic
  • Blogs à consulter: Jay Fields , Andy Glover , Nat Pryce
  • Comme mentionné dans d’autres réponses déjà:
    • XUnit Patterns
    • Odeurs de test
    • Blog de test Google
    • ” OO Design for Testability ” par Miskov Hevery
  • ” Evil Unit Tests ” par Paul Wheaton
  • ” Les tests d’intégration sont une arnaque ” par JB Rainsberger
  • ” L’économie de la conception de logiciels ” par JB Rainsberger
  • ” Développement piloté par les tests et méthode scientifique ” par Rick Mugridge
  • ” TDD comme si vous deviez le faire ” exercice à l’origine par Keith Braithwaite, également travaillé par Gojko Adzic

Testez-vous des unités de code suffisamment petites? Vous ne devriez pas voir trop de changements à moins que vous ne changiez fondamentalement tout dans votre code principal.

Une fois que les choses sont stables, vous apprécierez le fait que l’unité teste davantage, mais même maintenant, vos tests mettent en évidence la mesure dans laquelle les modifications apscopes à votre framework sont transmises.

Cela en vaut la peine, respectez-le du mieux que vous pouvez.

Sans plus d’informations, il est difficile de comprendre pourquoi vous souffrez de ces problèmes. Parfois, il est inévitable que le changement d’interface, etc., brise beaucoup de choses, d’autres fois, c’est dû à des problèmes de conception.

C’est une bonne idée d’essayer de classer les échecs par catégories. Quel genre de problèmes rencontrez-vous? Par exemple, est-ce qu’il teste la maintenance (comme en les faisant comstackr après le refactoring!) En raison des modifications de l’API, ou est-ce que le comportement de l’API change? Si vous pouvez voir un modèle, vous pouvez alors essayer de modifier la conception du code de production ou mieux isoler les tests.

Si le fait de modifier une poignée de choses cause des dégâts considérables à votre suite de tests dans de nombreux endroits, vous pouvez faire certaines choses (la plupart ne sont que des conseils de tests unitaires courants):

  • Développer de petites unités de code et tester de petites unités de code. Extrayez les interfaces ou les classes de base là où elles ont du sens, de sorte que les unités de code contiennent des «jointures». Plus vous aurez de dépendances (ou pire, instanciez dans la classe avec ‘new’), plus vous serez exposé à changer votre code. Si chaque unité de code a une poignée de dépendances (parfois un couple ou pas du tout), alors il est préférable de l’isoler du changement.

  • N’affirmez jamais ce dont le test a besoin. N’affirmez pas sur un état intermédiaire, accidentel ou indépendant. Conception par contrat et test par contrat (par exemple, si vous testez une méthode de stack pop, ne testez pas la propriété count après le push – cela devrait faire l’object d’un test séparé).

    Je vois ce problème assez, surtout si chaque test est une variante. Si l’un de ces états incidents change, il rompt tout ce qui y est associé (que les assertions soient nécessaires ou non).

  • Comme avec le code normal, utilisez les usines et les constructeurs dans vos tests unitaires. J’ai appris que lorsque 40 tests environ nécessitaient un appel de constructeur mis à jour après un changement d’API …

  • Tout aussi important, utilisez la porte d’entrée en premier. Vos tests doivent toujours utiliser l’état normal s’il est disponible. Seulement utilisé test basé sur l’interaction lorsque vous devez (c’est-à-dire pas d’état à vérifier).

Quoi qu’il en soit, j’essayerais de savoir pourquoi et où les tests vont et viennent. Faites de votre mieux pour vous protéger du changement.

L’un des avantages des tests unitaires est que lorsque vous apportez de telles modifications, vous pouvez prouver que vous ne cassez pas votre code. Vous devez garder vos tests en phase avec votre framework, mais ce travail plutôt banal est beaucoup plus facile que d’essayer de comprendre ce qui a été cassé lors de la refactorisation.

Je vous insiste pour restr avec le TDD. Essayez de vérifier votre cadre de test d’unité, faites une parsing de cause racine avec votre équipe et identifiez la zone.

Corrigez le code de test de l’unité au niveau de la suite et ne changez pas fréquemment votre code spécialement les noms de fonction ou d’autres modules.

Je vous serais reconnaissant si vous pouviez bien partager votre étude de cas, alors nous pourrions en savoir plus sur le problème?

Bonne question!

Concevoir de bons tests unitaires est difficile à concevoir en tant que tel. Ceci est rarement reconnu par les développeurs, et le résultat est souvent des tests unitaires écrits à la hâte qui nécessitent une maintenance chaque fois que le système testé change. Ainsi, une partie de la solution à votre problème pourrait consister à passer plus de temps à améliorer la conception de vos tests unitaires.

Je peux recommander un bon livre qui mérite sa facturation en tant que modèles de conception des tests unitaires

HTH

Si le problème est que vos tests ne sont plus à jour avec le code actuel, vous pouvez effectuer l’une des opérations suivantes:

  1. Former tous les développeurs à ne pas passer les revues de code qui ne mettent pas à jour les tests unitaires.
  2. Configurez une boîte de test automatique qui exécute l’ensemble des tests d’unités après chaque enregistrement et envoi par courrier électronique à ceux qui interrompent la génération. (Nous avions l’habitude de penser que cela ne concernait que les “grands garçons”, mais nous avons utilisé un paquetage open source sur une boîte dédiée.)

Eh bien, si la logique a changé dans le code et que vous avez des tests écrits pour ces parties de code, je suppose que les tests devront être modifiés pour vérifier la nouvelle logique. Les tests unitaires sont supposés être un code assez simple qui teste la logique de votre code.

Vos tests unitaires font ce qu’ils sont censés faire. Apportez à la surface des ruptures de comportement dues à des modifications du cadre, du code immédiat ou d’autres sources externes. Ce que vous devez faire est de vous aider à déterminer si le comportement a changé et si les tests unitaires doivent être modifiés en conséquence, ou si un bogue a été introduit, entraînant l’échec du test unitaire et nécessitant une correction.

N’abandonnez pas, même si c’est frustrant maintenant, le bénéfice sera réalisé.

Je ne suis pas sûr des problèmes spécifiques qui rendent difficile la maintenance des tests pour votre code, mais je peux partager certaines de mes propres expériences lorsque j’ai rencontré des problèmes similaires lors de la rupture de mes tests. J’ai finalement appris que le manque de testabilité était dû en grande partie à certains problèmes de conception de la classe testée:

  • Utiliser des classes concrètes au lieu d’interfaces
  • Utiliser des singletons
  • Appel de nombreuses méthodes statiques pour la logique métier et l’access aux données au lieu des méthodes d’interface

À cause de cela, j’ai constaté que mes tests étaient généralement en train de se briser – pas à cause d’un changement de classe testé – mais en raison de changements dans les autres classes appelées par la classe testée. En général, les classes de refactoring pour demander leurs dépendances de données et les tests avec des objects fictifs (EasyMock et al pour Java) rendent les tests beaucoup plus ciblés et maintenables. J’ai vraiment apprécié certains sites en particulier sur ce sujet:

  • Blog de test Google
  • Le guide pour écrire du code testable

Pourquoi devriez-vous modifier vos tests unitaires chaque fois que vous apportez des modifications à votre framework? Cela ne devrait-il pas être l’inverse?

Si vous utilisez TDD, vous devez d’abord décider que vos tests testent le comportement incorrect et qu’ils doivent plutôt vérifier que le comportement souhaité existe. Maintenant que vous avez corrigé vos tests, vos tests échouent et vous devez éliminer les bogues dans votre framework jusqu’à ce que vos tests soient de nouveau réussis.

Tout vient avec le prix du cours bien sûr. À ce stade précoce du développement, il est normal que de nombreux tests unitaires soient modifiés.

Vous voudrez peut-être examiner quelques bits de votre code pour faire plus d’encapsulation, créer moins de dépendances, etc.

Lorsque vous approchez de la date de production, vous serez heureux d’avoir ces tests, croyez-moi 🙂

Vos tests unitaires ne sont-ils pas trop orientés vers la boîte noire? Je veux dire … laissez-moi prendre un exemple: supposons que vous testez une sorte de conteneur, utilisez-vous la méthode get () du conteneur pour vérifier qu’un nouvel élément a bien été stocké, ou obtenez-vous un handle vers le stockage réel pour récupérer l’article directement où il est stocké? Le dernier fait des tests fragiles: lorsque vous modifiez l’implémentation, vous ne respectez pas les tests.

Vous devez tester les interfaces, pas l’implémentation interne.

Et quand vous changez de framework, vous feriez mieux d’essayer de changer les tests d’abord, puis le framework.

Je suggère d’investir dans un outil d’automatisation des tests. Si vous utilisez une continuous integration, vous pouvez la faire fonctionner en tandem. Il y a des outils là-bas qui vont scanner votre code et vont générer des tests pour vous. Alors les exécutera. L’inconvénient de cette approche est que c’est trop générique. Parce que dans de nombreux cas, le test unitaire a pour but de briser le système. J’ai écrit de nombreux tests et oui, je dois les changer si le code change.

Il y a une ligne fine avec l’outil d’automatisation dont vous auriez définitivement une meilleure couverture de code.

Cependant, avec des tests bien conçus, vous testerez également l’intégrité du système.

J’espère que cela t’aides.

Si votre code est vraiment difficile à tester et que le code de test se casse ou nécessite beaucoup d’efforts pour restr synchronisé, alors vous avez un problème plus important.

Envisagez d’utiliser le refactoring de la méthode extract pour extraire de petits blocs de code qui ne font qu’une chose et une seule chose. sans dépendances et écrivez vos tests sur ces petites méthodes.

Le code supplémentaire semble être devenu un casse-tête de maintenance, car lorsque notre framework interne change, nous devons corriger tous les tests unitaires qui y sont liés.

L’alternative est que lorsque votre Framework change, vous ne testez pas les modifications. Ou vous ne testez pas le Framework du tout. Est-ce que c’est ce que tu veux?

Vous pouvez essayer de refactoriser votre Framework afin qu’il soit composé d’éléments plus petits pouvant être testés indépendamment. Ensuite, lorsque votre structure change, vous espérez que (a) moins de pièces changent ou (b) les modifications concernent principalement la manière dont les pièces sont composées. De toute façon, vous obtiendrez une meilleure réutilisation du code et des tests. Mais un véritable effort intellectuel est impliqué; ne vous attendez pas à ce que ce soit facile.

Les tests unitaires finissent par tester l’interaction de plusieurs classes, ce qui les rend très complexes, et donc fragiles.

Ce que je veux dire, c’est que bon nombre des techniques de développement de logiciels novasortingces ne fonctionnent qu’en cas d’utilisation conjointe. En particulier MVC, ORM, IoC, tests unitaires et Mocking. Le DDD (au sens primitif moderne) et le TDD / BDD sont plus indépendants, vous pouvez donc les utiliser ou non.

Parfois, la conception des tests TDD lance des interrogations sur la conception de l’application elle-même. Vérifiez si vos classes ont été bien conçues, vos méthodes ne font qu’une chose à la fois … Avec une bonne conception, il devrait être simple d’écrire du code pour tester des méthodes et des classes simples.

J’ai moi-même réfléchi à ce sujet. Je suis très vendu sur la valeur des tests unitaires, mais pas sur le TDD ssortingct. Il me semble que, jusqu’à un certain point, vous pourriez faire de la programmation exploratoire où la façon dont les choses sont divisées en classes / interfaces devra être modifiée. Si vous avez investi beaucoup de temps dans les tests unitaires pour l’ancienne structure de classe, cela augmente l’inertie par rapport au refactoring, ce qui est pénible de rejeter ce code supplémentaire, etc.