Les tests unitaires - en particulier en C ++ - nécessitent du code testé d'ajouter plus de coutures qui seraient strictement nécessaires pour un problème donné.
Seulement si vous n'envisagez pas de tester une partie intégrante de la résolution de problèmes. Pour tout problème non trivial, il devrait l'être, non seulement dans le monde du logiciel.
Dans le monde du matériel, cela a été appris depuis longtemps - à la dure. Les fabricants de divers équipements ont appris au fil des siècles d'innombrables ponts qui tombent, des voitures qui explosent, des processeurs qui fument, etc., etc. ce que nous apprenons maintenant dans le monde du logiciel. Tous intègrent des «coutures supplémentaires» dans leurs produits afin de les rendre testables. De nos jours, la plupart des voitures neuves disposent de ports de diagnostic permettant aux réparateurs d'obtenir des données sur ce qui se passe à l'intérieur du moteur. Une partie importante des transistors de chaque CPU sert à des fins de diagnostic. Dans le monde du matériel, chaque élément «supplémentaire» coûte, et lorsqu'un produit est fabriqué par millions, ces coûts représentent certainement de grosses sommes d'argent. Pourtant, les fabricants sont prêts à dépenser tout cet argent pour la testabilité.
De retour au monde du logiciel, C ++ est en effet plus difficile à tester unitaire que les langages ultérieurs comportant un chargement de classe dynamique, une réflexion, etc. Pourtant, la plupart des problèmes peuvent être au moins atténués. Dans le projet C ++ où j'ai utilisé des tests unitaires jusqu'à présent, nous n'avons pas exécuté les tests aussi souvent que nous le ferions par exemple dans un projet Java - mais ils faisaient toujours partie de notre build CI, et nous les avons trouvés utiles.
Est-ce que la façon dont les tests unitaires nécessitent de structurer le code d'une application est "uniquement" bénéfique pour les tests unitaires ou est-ce réellement bénéfique pour la structure des applications?
D'après mon expérience, une conception testable est globalement bénéfique, pas "seulement" pour les tests unitaires eux-mêmes. Ces avantages se présentent à différents niveaux:
- Rendre votre conception testable vous oblige à découper votre application en petites parties plus ou moins indépendantes qui ne peuvent s'influencer les unes que les autres de manière limitée et bien définie - cela est très important pour la stabilité et la maintenabilité à long terme de votre programme. Sans cela, le code a tendance à se détériorer en code spaghetti où toute modification apportée à n'importe quelle partie de la base de code peut provoquer des effets inattendus dans des parties distinctes et apparemment indépendantes du programme. Inutile de dire que c'est le cauchemar de tout programmeur.
- L'écriture des tests eux-mêmes à la manière de TDD exerce en fait vos API, vos classes et vos méthodes et sert de test très efficace pour détecter si votre conception a du sens - si l'écriture de tests et l'interface vous semble gênante ou difficile, vous obtenez de précieuses informations précoces lorsqu'elle est encore facile de façonner l'API. En d'autres termes, cela vous empêche de publier vos API prématurément.
- Le modèle de développement imposé par TDD vous aide à vous concentrer sur la ou les tâches concrètes à faire et vous maintient sur la cible, minimisant les chances que vous vous éloigniez de résoudre d'autres problèmes que celui que vous êtes censé ajouter, ajoutant des fonctionnalités supplémentaires inutiles et de la complexité , etc.
- La rétroaction rapide des tests unitaires vous permet d'être audacieux dans la refactorisation du code, vous permettant d'adapter et d'évoluer constamment la conception au cours de la durée de vie du code, empêchant ainsi efficacement l'entropie du code.
Je me souviens d'une règle empirique qui disait de ne pas généraliser jusqu'à ce que vous ayez besoin de / jusqu'à ce qu'il y ait une deuxième place qui utilise le code. Avec les tests unitaires, il y a toujours une deuxième place qui utilise le code - à savoir le test unitaire. Cette raison est-elle donc suffisante pour généraliser?
Si vous pouvez prouver que votre logiciel fait exactement ce qu'il est censé faire - et le prouver d'une manière suffisamment rapide, reproductible, bon marché et déterministe pour satisfaire vos clients - sans la généralisation «supplémentaire» ou les coutures forcées par les tests unitaires, allez-y (et dites-nous comment vous le faites, car je suis sûr que beaucoup de gens sur ce forum seraient aussi intéressés que moi :-)
Btw Je suppose que par "généralisation", vous voulez dire des choses comme l'introduction d'une interface (classe abstraite) et le polymorphisme au lieu d'une seule classe concrète - sinon, veuillez clarifier.