Tests unitaires des composants internes

14

Dans quelle mesure testez-vous à l'unité les composants internes / privés d'une classe / module / package / etc? Les testez-vous ou testez-vous simplement l'interface avec le monde extérieur? Un exemple de ces méthodes internes est privé.

Par exemple, imaginez un analyseur de descente récursif , qui a plusieurs procédures internes (fonctions / méthodes) appelées à partir d'une procédure centrale. La seule interface avec le monde extérieur est la procédure centrale, qui prend une chaîne et renvoie les informations analysées. Les autres procédures analysent différentes parties de la chaîne et elles sont appelées à partir de la procédure centrale ou d'autres procédures.

Naturellement, vous devez tester l'interface externe en l'appelant avec des exemples de chaînes et en la comparant avec une sortie analysée à la main. Mais qu'en est-il des autres procédures? Souhaitez-vous les tester individuellement pour vérifier qu'ils analysent correctement leurs sous-chaînes?

Je peux penser à quelques arguments:

Avantages :

  1. Davantage de tests, c'est toujours mieux, et cela peut aider à augmenter la couverture du code
  2. Certains composants internes peuvent être difficiles à fournir des entrées spécifiques (cas de bord par exemple) en donnant une entrée à l'interface externe
  3. Des tests plus clairs. Si un composant interne a un bogue (corrigé), un cas de test pour ce composant indique clairement que le bogue était dans ce composant spécifique

Inconvénients :

  1. La refactorisation devient trop douloureuse et prend du temps. Pour changer quoi que ce soit, vous devez réécrire les tests unitaires, même si les utilisateurs de l'interface externe ne sont pas affectés
  2. Certains langages et frameworks de test ne le permettent pas

Quelles sont vos opinions?

imgx64
la source
doublon possible ou chevauchement significatif avec: programmers.stackexchange.com/questions/10832/…
azheglov

Réponses:

8

Cas: un "module" (au sens large, c'est-à-dire quelque chose ayant une interface publique et éventuellement aussi des parties internes privées) a une logique compliquée / impliquée à l'intérieur. Tester uniquement l'interface du module sera en quelque sorte un test d'intégration par rapport à la structure interne du module, et donc en cas d'erreur, ces tests ne localiseront pas la partie / le composant interne exact qui est responsable de la panne.

Solution: transformez les pièces internes complexes en modules eux-mêmes, testez-les à l'unité (et répétez ces étapes pour eux s'ils sont eux-mêmes trop compliqués) et importez-les dans votre module d'origine. Maintenant, vous avez juste un ensemble de modules assez simple pour uniit-test (à la fois vérifier que le comportement est correct et corriger les erreurs) facilement, et c'est tout.

Remarque:

  • il ne sera pas nécessaire de changer quoi que ce soit dans les tests des (anciens) "sous-modules" du module lors de la modification du contrat du module, à moins que le "sous-module" n'offre plus de services suffisants pour remplir le contrat nouveau / modifié.

  • rien ne sera rendu inutilement public, c'est-à-dire que le contrat du module sera conservé et l'encapsulation maintenue.

[Mise à jour]

Pour tester une logique interne intelligente dans les cas où il est difficile de mettre les parties internes de l'objet (je veux dire les membres et non les modules / packages importés en privé) dans un état approprié en alimentant simplement ses entrées via l'interface publique de l'objet:

  • ayez juste un peu de code de test avec un ami (en termes C ++) ou un accès au package (Java) aux entrailles en définissant réellement l'état de l'intérieur et en testant le comportement comme vous le souhaitez.

    • cela ne cassera pas l'encapsulation à nouveau tout en offrant un accès direct facile aux composants internes à des fins de test - exécutez simplement les tests comme une «boîte noire» et compilez-les dans les versions.
mlvljr
la source
et la disposition de la liste semble être un peu cassée; (
mlvljr
1
Bonne réponse. Dans .NET, vous pouvez utiliser l' [assembly: InternalsVisibleTo("MyUnitTestAssembly")]attribut dans votre AssemblyInfo.cspour tester les composants internes. Mais j'ai l'impression de tricher.
Personne
@rmx Une fois que quelque chose satisfait à tous les critères nécessaires, il n'y a pas de triche, même s'il a quelque chose en commun avec les tricheurs réels. Mais le sujet de l'accès inter / intra-module est vraiment un peu artificiel dans les langages traditionnels modernes.
mlvljr
2

L'approche du code basé sur FSM est un peu différente de celle utilisée traditionnellement. Il est très similaire à ce qui est décrit ici pour les tests de matériel (qui est généralement aussi un FSM).

En bref, vous créez une séquence d'entrée de test (ou un ensemble de séquences d'entrée de test) qui devrait non seulement produire une certaine sortie, mais également lorsque la production d'une sortie "mauvaise" particulière permet d'identifier le composant défaillant par la nature de la défaillance. L'approche est assez évolutive, plus vous passez de temps sur la conception du test, meilleur sera le test.

Ce type de test est plus proche de ce qu'on appelle les «tests fonctionnels», mais il élimine la nécessité de modifier les tests chaque fois que vous touchez légèrement une implémentation.

bobah
la source
2

En fait ça dépend :-). Si vous suivez une approche BDD (Behaviour Driven Development) ou ATDD (Acceptance Test Driven Development), tester l'interface publique est très bien (tant que vous la testez de manière exhaustive avec différentes entrées. L'implémentation sous-jacente, par exemple les méthodes privées, n'est pas en fait important.

Cependant, dites que vous voulez qu'une partie de cet algorithme s'exécute dans un certain laps de temps ou le long d'une certaine courbe bigO (par exemple nlogn), alors oui, testez la question des pièces individuelles. Certains appelleraient cela plus une approche TDD / Test unitaire traditionnelle.

Comme pour tout, YMMV

Martijn Verburg
la source
1

Diviser en plusieurs parties avec un sens fonctionnel, par exemple ParseQuotedString(), ParseExpression(), ParseStatement(), ParseFile()et les rendre tout public. Quelle est la probabilité que votre syntaxe change tellement que celles-ci deviennent inutiles?

DomQ
la source
1
cette approche conduit facilement à une encapsulation plus faible et à des interfaces plus grandes et plus difficiles à utiliser / à comprendre.
sara