Test unitaire C ++: que tester?

20

TL; DR

Écrire de bons tests utiles est difficile et a un coût élevé en C ++. Pouvez-vous des développeurs expérimentés partager votre justification sur quoi et quand tester?

Longue histoire

J'avais l'habitude de faire du développement piloté par les tests, toute mon équipe en fait, mais cela ne fonctionnait pas bien pour nous. Nous avons de nombreux tests, mais ils ne semblent jamais couvrir les cas où nous avons de réels bugs et régressions - qui se produisent généralement lorsque les unités interagissent, et non de leur comportement isolé.

C'est souvent si difficile à tester au niveau de l'unité que nous avons cessé de faire TDD (sauf pour les composants où cela accélère vraiment le développement), et avons plutôt investi plus de temps pour augmenter la couverture des tests d'intégration. Bien que les petits tests unitaires n'aient jamais détecté de vrais bogues et n'étaient essentiellement que des frais de maintenance, les tests d'intégration en valaient vraiment la peine.

Maintenant, j'ai hérité d'un nouveau projet et je me demande comment procéder pour le tester. C'est une application native C ++ / OpenGL, donc les tests d'intégration ne sont pas vraiment une option. Mais les tests unitaires en C ++ sont un peu plus difficiles qu'en Java (vous devez faire explicitement des trucs virtual), et le programme n'est pas fortement orienté objet, donc je ne peux pas me moquer de certaines choses.

Je ne veux pas déchirer et OO-ize le tout juste pour écrire des tests pour le plaisir d'écrire des tests. Je vous demande donc: pour quoi dois-je écrire des tests? par exemple:

  • Fonctions / classes que je m'attends à changer fréquemment?
  • Fonctions / classes plus difficiles à tester manuellement?
  • Fonctions / Classes déjà faciles à tester?

J'ai commencé à enquêter sur quelques bases de code C ++ respectueuses pour voir comment elles procèdent aux tests. En ce moment, je regarde le code source de Chromium, mais j'ai du mal à extraire leur justification de test du code. Si quelqu'un a un bon exemple ou un article sur la popularité des utilisateurs de C ++ (gars du comité, auteurs de livres, Google, Facebook, Microsoft, ...), ce serait très utile.

Mise à jour

J'ai cherché sur ce site et sur le Web depuis que j'ai écrit ceci. J'ai trouvé de bonnes choses:

Malheureusement, tous ces éléments sont plutôt centrés sur Java / C #. L'écriture de nombreux tests en Java / C # n'est pas un gros problème, donc l'avantage surpasse généralement les coûts.

Mais comme je l'ai écrit ci-dessus, c'est plus difficile en C ++. Surtout si votre base de code n'est pas si-OO, vous devez sérieusement gâcher les choses pour obtenir une bonne couverture de test unitaire. Par exemple: l'application dont j'ai hérité a un Graphicsespace de nom qui est une fine couche au-dessus d'OpenGL. Afin de tester l'une des entités - qui utilisent toutes directement ses fonctions - je devrais transformer cela en une interface et une classe et l'injecter dans toutes les entités. Ce n'est qu'un exemple.

Donc, en répondant à cette question, gardez à l'esprit que je dois faire un investissement assez important pour écrire des tests.

futlib
la source
3
+1 pour la difficulté des tests unitaires C ++. Si votre test unitaire vous oblige à changer le code, ne le faites pas.
DPD
2
@DPD: Je ne suis pas sûr, que se passe-t-il si quelque chose vaut vraiment la peine d'être testé? Dans la base de code actuelle, je peux à peine tester quoi que ce soit dans le code de simulation, car il appelle tous les fonctions graphiques directement, et je ne peux pas les simuler / tronquer. Tout ce que je peux tester en ce moment, ce sont les fonctions utilitaires. Mais je suis d'accord, changer le code pour le rendre "testable" semble ... mal. Les partisans de TDD disent souvent que cela rendra tout votre code meilleur de toutes les manières imaginables, mais je suis humblement en désaccord. Tout n'a pas besoin d'une interface et de plusieurs implémentations.
futlib
Permettez-moi de vous donner un exemple récent: j'ai passé une journée entière à tester une seule fonction (écrite en C ++ / CLI) et l'outil de test MS Test plantait toujours pour ce test. Il semblait y avoir un problème avec les références simples du RPC. Au lieu de cela, je viens de tester la sortie de sa fonction d'appel et cela a bien fonctionné. J'ai perdu une journée entière à UT une fonction. Ce fut une perte de temps précieux. De plus, je n'ai pas pu obtenir d'outil de tronçonnage adapté à mes besoins. J'ai fait du stubbing manuel dans la mesure du possible.
DPD
C'est juste le genre de choses que j'aimerais éviter: je suppose que nous, les développeurs C ++, devons être particulièrement pragmatiques à propos des tests. Vous avez fini par le tester, donc je suppose que c'est OK.
futlib
@DPD: J'y ai réfléchi un peu plus, et je pense que vous avez raison, la question est de savoir quel type de compromis je veux faire. Vaut-il la peine de refactoriser l'ensemble du système graphique pour tester quelques entités? Il n'y avait aucun bogue à ma connaissance, donc probablement: Non. S'il commence à se sentir bogué, j'écrirai des tests. Dommage que je ne puisse pas accepter votre réponse car c'est un commentaire :)
futlib

Réponses:

5

Eh bien, les tests unitaires ne sont qu'une partie. Les tests d'intégration vous aident à résoudre le problème de votre équipe. Les tests d'intégration peuvent être écrits pour toutes sortes d'applications, également pour les applications natives et OpenGL. Vous devriez consulter "Growing Object Oriented Software Guided by Tests" de Steve Freemann et Nat Pryce (par exemple http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Signature/dp/0321503627 ). Il vous guide pas à pas dans le développement d'une application avec interface graphique et communication réseau.

Tester un logiciel qui n'était pas piloté par les tests est une autre histoire. Consultez Michael Feathers "Working Effectiveively with Legacy Code" (http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052).

EricSchaefer
la source
Je connais les deux livres. Les choses sont: 1. Nous ne voulons pas aller avec TDD, parce que cela n'a pas bien fonctionné avec nous. Nous voulons des tests, mais pas religieusement. 2. Je suis sûr que les tests d'intégration pour les applications OpenGL sont possibles d'une manière ou d'une autre, mais cela demande trop d'efforts. Je veux continuer à améliorer l'application, pas lancer un projet de recherche.
futlib
Sachez que les tests unitaires "après coup" sont toujours plus difficiles que "tester d'abord", car le code n'est pas conçu pour être testable (réutilisable, maintenable, etc.). Si vous voulez quand même le faire, essayez de vous en tenir aux astuces de Michael Feathers (par exemple les coutures) même si vous évitez le TDD. Par exemple, si vous voulez étendre une fonction, essayez quelque chose comme "méthode sprout" et essayez de garder la nouvelle méthode testable. Il est possible de le faire, mais plus difficile à mon humble avis.
EricSchaefer
Je suis d'accord que le code non TDD n'est pas conçu pour être testable, mais je ne dirais pas qu'il n'est pas maintenable ou réutilisable en soi - comme je l'ai commenté ci-dessus, certaines choses n'ont tout simplement pas besoin d'interfaces et d'implémentations multiples. Ce n'est pas un problème du tout avec Mockito, mais en C ++, je dois rendre virtuelles toutes les fonctions que je veux stub / mock. Quoi qu'il en soit, le code non testable est mon plus gros problème en ce moment: je dois changer des choses très fondamentales pour rendre certaines parties testables, et donc je veux une bonne justification sur ce qu'il faut tester, pour m'assurer qu'il en vaut la peine.
futlib
Vous avez bien sûr raison, je ferai attention à rendre tout nouveau code que j'écris testable. Mais ce ne sera pas facile, avec la façon dont les choses fonctionnent actuellement dans cette base de code.
futlib
Lorsque vous ajoutez une fonction / fonction, pensez simplement à la façon dont vous pouvez la tester. Pourriez-vous injecter des dépendances laides? Comment sauriez-vous que la fonction fait ce qu'elle est censée faire. Pourriez-vous observer un comportement? Y a-t-il un résultat dont vous pourriez vérifier l'exactitude? Y a-t-il des invariants que vous pouvez vérifier?
EricSchaefer
2

C'est dommage que TDD "n'ait pas bien fonctionné pour vous". Je pense que c'est la clé pour comprendre vers qui se tourner. Revoyez et comprenez comment TDD n'a pas fonctionné, que pourriez-vous faire de mieux, pourquoi y a-t-il eu des difficultés.

Donc, bien sûr, vos tests unitaires n'ont pas détecté les bogues que vous avez trouvés. C'est un peu le point. :-) Vous n'avez pas trouvé ces bogues parce que vous les avez empêchés de se produire en premier en réfléchissant à la façon dont les interfaces devraient fonctionner et comment vous assurer qu'elles ont été testées correctement.

Pour répondre, vous vous interrogez, comme vous l'avez conclu, sur le code de test unitaire qui n'est pas conçu pour être testé est difficile. Pour le code existant, il peut être plus efficace d'utiliser un environnement de test fonctionnel ou d'intégration plutôt qu'un environnement de test unitaire. Testez l'ensemble du système en vous concentrant sur des domaines spécifiques.

Bien entendu, les nouveaux développements bénéficieront de TDD. Au fur et à mesure que de nouvelles fonctionnalités sont ajoutées, le refactoring pour TDD pourrait aider à tester le nouveau développement, tout en permettant également le développement de nouveaux tests unitaires pour les fonctions héritées.

Bill Door
la source
4
Nous avons fait du TDD pendant environ un an et demi, tous assez passionnés. Pourtant, en comparant les projets TDD avec des projets antérieurs réalisés sans TDD (mais pas sans tests), je ne dirais pas qu'ils sont en fait plus stables ou ont un code mieux conçu. Peut-être que c'est notre équipe: nous couplons et révisons beaucoup, la qualité de notre code a toujours été plutôt bonne.
futlib
1
Plus j'y pense, plus je pense que TDD ne correspondait pas très bien à la technologie de ce projet particulier: Flex / Swiz. Il y a beaucoup d'événements et de liaisons et d'injections qui rendent les interactions entre les objets compliquées et presque impossibles à tester unitairement. Le découplage de ces objets ne le rend pas meilleur, car ils fonctionnent correctement en premier lieu.
futlib
2

Je n'ai pas fait TDD en C ++ donc je ne peux pas commenter cela, mais vous êtes censé tester le comportement attendu de votre code. Bien que l'implémentation puisse changer, le comportement devrait (généralement?) Rester le même. Dans le monde centré sur Java \ C #, cela signifierait que vous testez uniquement les méthodes publiques, que vous écrivez des tests pour le comportement attendu et que vous le faites avant l'implémentation (ce qui est généralement mieux dit que fait :)).

Dante
la source