Génération automatisée de cas de test JUnit et couverture de code
Par Arthur Hicken
le 16 mars 2018
6 min lire
Si la couverture de code est un problème pour vous, assurez-vous de la mesurer correctement et de la mesurer à partir de tous les tests que vous exécutez. Tirez parti de la génération automatique de cas de test de couverture de code JUnit pour créer et étendre rapidement vos tests afin d'obtenir une couverture de code complète significative et maintenable. La couverture des tests unitaires est un excellent moyen de vous assurer que vous mesurez tout correctement.
Plongez-vous dans les problèmes et solutions de couverture du code
J'ai récemment écrit un article sur à quel point il est facile de tomber dans le piège de la poursuite des pourcentages de couverture de code, ce qui a conduit à des discussions de qualité, j'ai donc pensé que j'allais approfondir les problèmes et les solutions de couverture de code. Plus précisément, le numéro de couverture lui-même, la valeur des tests JUnit générés automatiquement et la manière dont vous pouvez identifier les tests unitaires qui posent des problèmes. Et comment continuer à faire un meilleur travail avec l'exécution.
Comment fonctionne la couverture de code JUnit?
Commençons par la métrique de couverture elle-même et comment nous comptons la couverture de code. Les numéros de couverture de code sont souvent dénués de sens ou, au mieux, trompeurs. Si vous avez une couverture de code à 100%, qu'est-ce que cela signifie? Comment l'avez-vous mesuré?
Il existe de nombreuses méthodes différentes pour mesurer la couverture.
Une façon de mesurer la couverture du code est du point de vue des exigences. Avez-vous un test pour chaque exigence? C'est un début raisonnable… mais cela ne signifie pas que tout le code a été testé.
Une autre façon de mesurer la couverture du code (ne riez pas, j'entends cela dans le monde réel) est le nombre de tests réussis. Vraiment, je le pense! C'est une métrique assez horrible et évidemment dénuée de sens. Est-ce pire ou mieux que de simplement compter le nombre de tests que vous avez? Je ne pourrais pas dire.
Ensuite, nous en venons à essayer de déterminer quel code a été exécuté. Les mesures de couverture courantes incluent la couverture des relevés, la couverture des lignes, la couverture des succursales, la couverture des décisions, la couverture des conditions multiples ou MC / DC or Couverture de la condition / décision modifiée.
La méthode la plus simple, bien sûr, est la couverture de ligne, mais comme vous l'avez probablement vu, les outils mesurent cela différemment, donc la couverture sera différente. Et exécuter des lignes de code ne signifie pas que vous avez vérifié toutes les différentes choses qui peuvent se produire dans cette ligne de code. C'est pourquoi les normes critiques pour la sécurité comme ISO 26262 pour la sécurité fonctionnelle automobile et DO-178B / C pour les systèmes embarqués nécessitent MC / DC.
Voici un exemple de code simple, en supposant que x, y et z sont des booléens:
Si ((x || y) && z) {doSomethingGood (); } else {doSomethingElse ();}
Dans ce cas, quelles que soient mes valeurs, la ligne a été "couverte". Certes, c'est une façon bâclée de coder en mettant tout sur une seule ligne, mais vous voyez le problème. Et les gens écrivent du code de cette façon. Mais nettoyons-le un peu.
Si ((x || y) && z) {
doSomethingGood ();
} Else {
doSomethingElse (); / * parce que le code ne devrait jamais faire quelque chose de mauvais () * /
Un simple coup d'œil pourrait m'amener à la conclusion que j'ai juste besoin de deux tests - un qui évalue l'expression entière à TRUE et exécute doSomethingGood () (x = vrai, y = vrai, z = vrai), et un autre test qui évalue FALSE et exécute doSomethingElse () (x = faux, y = faux, z = faux). La couverture de ligne indique que nous sommes prêts à partir: "Tout a été testé."
Mais attendez une minute, il existe différentes manières de tester l'expression principale:
Valeur de x | Valeur de y | Valeur de z | Valeur de la décision |
---|---|---|---|
Faux | Faux | Vrai | Faux |
Faux | Vrai | Vrai | Vrai |
Faux | Vrai | Faux | Faux |
Vrai | Faux | Vrai | Vrai |
Ceci est un exemple simple, mais il illustre ce point. J'ai besoin de 4 tests ici pour vraiment couvrir correctement le code, du moins si je me soucie de la couverture MC / DC. La couverture de ligne aurait dit 100% quand j'avais à moitié fini. Je laisserai l'explication plus longue sur la valeur de MC / DC pour une autre fois. Le point ici est que quelle que soit la méthode que vous utilisez pour mesurer la couverture, il est important que ce que vous validez par des assertions soit significatif.
Couverture complète du code: couverture globale des pratiques de test
Génération automatique sans signification
Un autre piège dans lequel beaucoup tombent est d'ajouter un outil peu sophistiqué pour générer automatiquement des tests unitaires.
Des outils simples de génération de tests créent des tests qui exécutent du code sans aucune assertion. Cela empêche les tests d'être bruyants, mais tout ce que cela signifie vraiment, c'est que votre application ne plante pas. Malheureusement, cela ne vous dit pas si l'application fait ce qu'elle est censée faire, ce qui est très important.
La prochaine génération d'outils fonctionne en créant des assertions basées sur des valeurs particulières qu'ils peuvent capturer automatiquement. Cependant, si la génération automatique crée une tonne d'assertions, vous vous retrouvez avec une tonne de bruit. Il n'y a pas de terrain d'entente ici. Soit vous avez quelque chose de facile à entretenir mais sans signification, soit un cauchemar d'entretien dont la valeur est discutable.
De nombreux outils open source qui génèrent automatiquement des tests unitaires semblent précieux au début car votre couverture augmente très rapidement. C'est dans la maintenance que les vrais problèmes surviennent. Souvent, au cours du développement, les développeurs déploient des efforts supplémentaires pour affiner les assertions générées automatiquement afin de créer ce qu'ils pensent être une suite de tests propre. Cependant, les assertions sont fragiles et ne s'adaptent pas lorsque le code change. Cela signifie que les développeurs doivent recommencer une grande partie de la génération «automatique» la prochaine fois qu'ils publient. Les suites de tests sont destinées à être réutilisées. Si vous ne pouvez pas les réutiliser, vous faites quelque chose de mal.
Cela ne couvre pas non plus l'idée plus effrayante que lors de la première exécution, lorsque vous avez une couverture élevée, les affirmations contenues dans les tests sont moins significatives qu'elles ne devraient l'être. Ce n'est pas parce que quelque chose peut être affirmé, que cela devrait l'être, ou que c'est même la bonne chose.
classe publique ListTest {
liste privée list = new ArrayList <> ();
@Tester
public void testAdd () {
list.add («Foo»);
assertNotNull (liste);
}
}
Idéalement, l'assertion vérifie que le code fonctionne correctement et l'assertion échouera lorsque le code ne fonctionne pas correctement. Il est vraiment facile d'avoir un tas d'assertions qui ne font ni l'un ni l'autre, que nous explorerons ci-dessous.
Couverture brute vs. Tests significatifs
Si vous recherchez un nombre à couverture élevée au détriment d'une suite de tests solide, significative et propre, vous perdez de la valeur. Une suite de tests bien entretenue vous donne confiance en votre code et constitue même la base d'une refactorisation rapide et sûre. Des tests bruyants et / ou dénués de sens signifient que vous ne pouvez pas compter sur votre suite de tests, ni pour la refactorisation, ni même pour la publication.
Ce qui se passe lorsque les gens mesurent leur code, en particulier par rapport à des normes strictes, c'est qu'ils découvrent qu'il est inférieur à ce qu'ils veulent. Et souvent, cela se termine par la poursuite du numéro de couverture. Mettons la couverture en place! Et maintenant, vous pouvez entrer en territoire dangereux soit par une croyance déraisonnable que les tests JUnit automatisés ont créé des tests significatifs, soit en créant des tests unitaires à la main qui ont peu de sens et sont coûteux à maintenir.
Dans le monde réel, les coûts permanents de maintenance d'une suite de tests dépassent de loin les coûts de création de tests unitaires, il est donc important que vous créiez de bons tests unitaires propres au début. Vous le saurez car vous pourrez exécuter les tests tout le temps dans le cadre de votre processus d'intégration continue (CI). Si vous n'exécutez les tests qu'à la sortie, c'est un signe que les tests sont plus bruyants qu'ils ne devraient l'être. Et ironiquement, cela rend les tests encore pires car ils ne sont pas maintenus.
L'automatisation des tests logiciels n'est pas mauvaise - en fait, elle est nécessaire, avec la complexité et les contraintes de temps qui sont courantes aujourd'hui. Mais la génération automatique de valeurs est généralement plus compliquée qu'elle n'en vaut la peine. L'automatisation basée sur l'expansion des valeurs, la surveillance de systèmes réels et la création de cadres complexes, de simulacres et de stubs offrent plus de valeur qu'une création inconsidérée d'assertions.
Que pouvez-vous faire?
Measure
La première étape consiste à mesurer et à obtenir un rapport sur votre couverture actuelle, sinon vous ne saurez pas où vous en êtes et si vous vous améliorez. Il est important de mesurer toutes les activités de test lors de cette opération, y compris l'unité, le fonctionnement, le manuel, etc., et d'agréger correctement la couverture. De cette façon, vous déploierez vos efforts là où cela a le plus de valeur - sur du code qui n'est pas du tout testé, plutôt que sur du code qui est couvert par vos tests de bout en bout mais qui n'a pas Test de l'unité. Parasoft peut agréger avec précision la couverture du code à partir de plusieurs exécutions et de plusieurs types de tests pour vous donner une mesure précise de votre situation. Pour en savoir plus, consultez notre livre blanc.
Automatisez la création de tests Junit et commencez à aimer les tests unitaires.
Essayez Parasoft Jtest maintenant
Cadres
Les outils qui créent pour vous des squelettes de tests unitaires sont un bon moyen de commencer. Assurez-vous que ces outils se connectent aux frameworks moqueurs courants tels que Mockito et PowerMock, car le vrai code est compliqué et nécessite un stubbing et une moquerie. Mais ce n'est pas suffisant - vous devez être capable de:
- Créez des simulations significatives
- Développez des tests simples avec des données plus volumineuses et plus larges
- Surveiller une application en cours d'exécution
Assistance intelligente
Vous pouvez faire toutes ces choses manuellement, mais cela prend trop de temps et d'efforts. C'est un excellent endroit pour tirer parti de l'automatisation - par exemple, La solution de test unitaire de Parasoft fournit des recommandations automatiques en temps réel dans l'IDE, s'intégrant aux frameworks open source (JUnit, Mockito, PowerMock, etc.) pour aider l'utilisateur à créer, mettre à l'échelle et maintenir sa suite de tests JUnit et fournir une couverture plus large. Si cette technologie vous intéresse, vous pouvez en savoir plus sur les raisons pour lesquelles les gens détestent les tests unitaires et comment ramener l'amour.
Résumé
Si la couverture est un problème pour votre projet, assurez-vous de la mesurer correctement et de la mesurer TOUT à partir de tous les tests que vous exécutez. Et lorsque vous commencez à étendre votre couverture avec des tests unitaires, vous pouvez tirer parti de la création de tests guidés pour créer et étendre rapidement vos tests afin d'obtenir une couverture de code maintenable significative. Jtest Parasoft créera des tests qui seront maintenables au fur et à mesure que votre code grandit et change, de sorte que vous ne faites pas le même travail encore et encore.