Écrire des tests pour le code existant

68

Supposons que l’on ait un programme relativement important (disons 900k SLOC en C #), tous commentés / documentés de manière approfondie, bien organisés et fonctionnant bien. L'ensemble de la base de code a été écrit par un seul développeur senior qui n'est plus avec la société. Tout le code est testable tel quel et IoC est utilisé partout - sauf pour une raison étrange, ils n’ont écrit aucun test unitaire. Désormais, votre société souhaite créer une branche pour le code et souhaite ajouter des tests unitaires afin de détecter le moment où des modifications annulent la fonctionnalité principale.

  • L'ajout de tests est-il une bonne idée?
  • Si oui, comment pourrait-on même commencer avec quelque chose comme ça?

MODIFIER

OK, donc je ne m'attendais pas à des réponses en faisant de bons arguments pour des conclusions opposées. La question est peut-être hors de portée de toute façon. J'ai également lu les "questions en double" et le consensus général est que "passer des tests, c'est bien" ... oui, mais pas très utile dans ce cas particulier.

Je ne pense pas que je suis le seul à envisager d’écrire des tests pour un système existant. Je vais garder des mesures sur combien de temps est passé et combien de fois les nouveaux tests détectent des problèmes (et combien de fois ils ne le font pas). Je reviendrai et le mettrai à jour dans un an environ avec mes résultats.

CONCLUSION

Il s'avère donc qu'il est fondamentalement impossible d'ajouter un test unitaire au code existant avec un semblant d'orthodoxie. Une fois que le code fonctionne, vous ne pouvez évidemment pas mettre vos tests à feu rouge ou vert, il n’est généralement pas clair quels comportements sont importants à tester, ni où commencer et surtout quand vous avez terminé. Vraiment même poser cette question passe à côté de l’essentiel de l’écriture des tests. Dans la majorité des cas, j’ai trouvé qu’il était en fait plus facile de réécrire le code avec TDD que de déchiffrer les fonctions prévues et d’ajouter rétroactivement des tests unitaires. Lors de la résolution d'un problème ou de l'ajout d'une nouvelle fonctionnalité, l'histoire est différente et je pense que le moment est venu d'ajouter des tests unitaires (comme certains l'ont souligné ci-dessous). Finalement, la plupart du code est réécrit, souvent plus tôt que prévu, en adoptant cette approche.

Paul
la source
10
L'ajout de tests ne cassera pas le code existant.
Dan Pichelman
30
@DanPichelman Vous n'avez jamais rencontré un schroedinbug - "Un bogue de conception ou d'implémentation dans un programme qui ne se manifeste pas avant que quelqu'un lisant une source ou l'utilisant de manière inhabituelle ne remarque que cela aurait dû ne jamais fonctionner, puis le programme se met rapidement cesse de fonctionner pour tout le monde jusqu'à ce que fixé ".
8
@MichaelT Maintenant que vous en parlez, je pense en avoir vu un ou deux. Mon commentaire aurait dû se lire "L'ajout de tests ne casse généralement pas le code existant". Merci
Dan Pichelman
3
N'écrivez que des tests autour de choses que vous avez l'intention de modifier ou de modifier.
Steven Evers
3
Consultez le livre "Travailler efficacement avec le code hérité": amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/… , Michael Feathers suggère d'écrire les tests avant de modifier le code hérité.
Skarab

Réponses:

68

Bien que les tests soient une bonne idée, le codeur d'origine avait l'intention de les construire, car il était en train de créer l'application, afin de saisir sa connaissance du fonctionnement du code et de ce qui pourrait casser, ce qui vous aurait ensuite été transféré.

En adoptant cette approche, il est très probable que vous rédigiez les tests les moins susceptibles de casser et que vous manquiez la plupart des cas extrêmes qui auraient été découverts lors de la construction de l'application.

Le problème est que la plus grande partie de la valeur proviendra de ces "pièges" et de situations moins évidentes. Sans ces tests, la suite de tests perd pratiquement toute son efficacité. En outre, la société aura un faux sentiment de sécurité autour de leur application car elle ne sera pas beaucoup plus résistante à la régression.

Généralement, la manière de gérer ce type de base de code consiste à écrire des tests pour le nouveau code et pour le refactoring de l'ancien code jusqu'à ce que la base de code existante soit entièrement refactorisée.

Aussi voir .

FMJaguar
la source
11
Mais, même sans TDD, les tests unitaires sont utiles lors du refactoring.
pdr
1
Si le code continue à bien fonctionner, ce n'est pas un problème, mais la meilleure chose à faire est de tester l'interface avec le code hérité chaque fois que vous écrivez quoi que ce soit qui repose sur son comportement.
deworde
1
C'est pourquoi vous mesurez la couverture du test . Si le test d'une section particulière ne couvre pas tous les ifs et elses et tous les cas extrêmes, vous ne pouvez pas refactoriser cette section en toute sécurité. La couverture vous indiquera si toutes les lignes sont touchées. Votre objectif est donc d'augmenter la couverture autant que possible avant la refactorisation.
Rudolf Olah
3
Un inconvénient majeur de TDD est que, même si la suite peut être exécutée, le développeur n’est pas familiarisé avec la base de code, il s’agit là d’un faux sentiment de sécurité. BDD est bien meilleur à cet égard car le résultat est l’ intention du code en clair.
Robbie Dee
3
Tout comme pour mentionner que la couverture de code à 100% ne signifie pas que votre code fonctionne correctement dans 100% des cas. Vous pouvez avoir chaque ligne de code testée pour la méthode, mais ce n'est pas parce que cela fonctionne avec valeur1 que cela fonctionne avec valeur2.
Ryanzec
35

Oui, l'ajout de tests est certainement une bonne idée.

Vous dites que c'est bien documenté et que cela vous met dans une bonne position. Essayez de créer des tests en utilisant cette documentation comme guide, en vous concentrant sur les parties du système critiques ou sujettes à de fréquentes modifications.

Au départ, la taille même de la base de code vous semblera probablement trop lourde comparée à une quantité infime de tests, mais il n’ya pas d’approche globale, et il est plus important de commencer quelque part que de se demander quelle sera la meilleure approche.

Je recommanderais le livre de Michael Feathers, Travailler efficacement avec Legacy Code , pour de bons conseils détaillés.

Danny Woods
la source
10
+1 Si vous écrivez les tests sur la base de la documentation, vous découvrirez des différences entre le code de travail et la documentation, ce qui est inestimable.
Carl Manaster
1
Une autre raison d'ajouter des tests: lorsqu'un bogue est détecté, vous pouvez facilement ajouter un scénario de test pour les tests de régression futurs.
Cette stratégie est essentiellement celle décrite dans le cours (gratuit) en ligne edX CS169.2x du chapitre consacré au code hérité. Comme le disent les enseignants: "Établir la vérité sur le terrain avec des tests de caractérisation" au chapitre 9 du livre: beta.saasbook.info/table-of-contents
FGM
21

Tous les tests unitaires ne présentent pas les mêmes avantages. L'avantage d'un test unitaire vient quand il échoue. Moins il est probable qu’il échoue, moins il est bénéfique. Le code nouveau ou récemment modifié est plus susceptible de contenir des bogues que le code rarement modifié qui est bien testé en production. Par conséquent, les tests unitaires sur le code nouveau ou récemment modifié ont plus de chances d’être plus bénéfiques.

Tous les tests unitaires n'ont pas le même coût. Il est beaucoup plus facile de tester à l'unité le code trivial que vous avez conçu vous-même aujourd'hui que le code complexe créé il y a longtemps par quelqu'un d'autre. En outre, les tests en cours de développement permettent généralement de gagner du temps. Sur le code existant, les économies de coûts ne sont plus disponibles.

Dans un monde idéal, vous disposeriez de tout le temps nécessaire pour tester les codes existants, mais dans la réalité, il est logique de penser que l'ajout de tests unitaires au code hérité l'emportera sur les avantages. L'astuce consiste à identifier ce point. Votre contrôle de version peut vous aider en vous montrant le dernier code modifié et le plus fréquemment modifié, et vous pouvez commencer par le soumettre à un test unitaire. En outre, lorsque vous apportez des modifications, soumettez-les à un test unitaire.

En suivant cette méthode, vous aurez éventuellement une assez bonne couverture dans les domaines les plus bénéfiques. Si au lieu de cela, vous passez des mois à mettre en place des tests unitaires avant de reprendre des activités génératrices de revenus, cela pourrait être une décision de maintenance logicielle souhaitable, mais une décision de mauvaise qualité.

Karl Bielefeldt
la source
3
Si le code échoue rarement, vous pourriez consacrer beaucoup de temps et d'efforts à la recherche de problèmes mystérieux qui ne pourraient jamais se produire dans la vie réelle. D'un autre côté, si le code est sujet à des erreurs et à des erreurs, vous pouvez probablement commencer à tester n'importe où et trouver les problèmes immédiatement.
Robbie Dee
8

L'ajout de tests est-il une bonne idée?

Absolument, même si j’ai un peu de mal à croire que le code soit propre, qu’il fonctionne bien et qu’il utilise des techniques modernes et qu’il n’ait simplement pas de tests unitaires. Êtes-vous sûr qu'ils ne sont pas assis dans une solution séparée?

Quoi qu'il en soit, si vous souhaitez étendre / maintenir le code, alors les vrais tests unitaires sont inestimables pour ce processus.

Si oui, comment pourrait-on même commencer avec quelque chose comme ça?

Un pas après l'autre. Si vous n'êtes pas familier avec les tests unitaires, apprenez-en un peu. Une fois que vous êtes à l'aise avec les concepts, choisissez une petite section du code et écrivez des tests à ce sujet. Ensuite le suivant et le suivant. La couverture de code peut vous aider à trouver des endroits que vous avez manqués.

Il est probablement préférable de commencer par tester les éléments dangereux / risqués / vitaux à tester, mais vous pourriez être plus efficace de tester quelque chose de simple pour entrer dans un sillon - en particulier si vous / l'équipe n'êtes pas habitué au code ou à l'unité essai.

Telastyn
la source
7
"Etes-vous sûr qu'ils ne sont pas assis dans une solution séparée?" C'est une excellente question. J'espère que le PO ne l'oublie pas.
Dan Pichelman
Malheureusement, l'application a été lancée il y a de nombreuses années, au moment même où TDD gagnait du terrain. L'intention était de faire des tests à un moment donné, mais pour une raison quelconque, une fois le projet démarré, ils ne l'ont jamais fait.
Paul
2
Ils n'y sont probablement jamais parvenus parce que cela leur aurait pris du temps supplémentaire qui n'en valait pas la peine. Un bon développeur qui travaille seul peut créer une application propre, claire, bien organisée et fonctionnelle de taille relativement importante sans aucun test automatisé. En général, il peut le faire plus rapidement et aussi facilement que les tests. Comme tout est dans leur tête, il y a beaucoup moins de risques de bogues ou de problèmes d'organisation que si plusieurs développeurs le créaient.
Ben Lee
3

Oui, avoir des tests est une bonne idée. Ils aideront à documenter le fonctionnement de la base de code existant comme prévu et à détecter tout comportement inattendu. Même si les tests échouent initialement, laissez-les, puis refactorisez le code ultérieurement afin qu'ils réussissent et se comportent comme prévu.

Commencez à écrire des tests pour des classes plus petites (celles qui n'ont pas de dépendances et sont relativement simples) et passez à des classes plus grandes (celles qui ont des dépendances et sont plus complexes). Cela prendra beaucoup de temps, mais soyez patient et persévérant afin de pouvoir couvrir le plus possible la base de code.

Bernard
la source
Souhaitez-vous vraiment ajouter les tests qui échouent à un programme qui fonctionne bien (le PO dit)?
MarkJ
Oui, car cela montre que quelque chose ne fonctionne pas comme prévu et nécessite un examen plus approfondi. Cela suscitera une discussion et, espérons-le, corrigera tout malentendu ou tout défaut précédemment inconnu.
Bernard
@ Bernard - ou, les tests peuvent révéler votre incompréhension de ce que le code est censé faire. Les tests écrits après coup risquent de ne pas résumer correctement les intentions initiales.
Dan Pichelman
@DanPichelman: D'accord, mais cela ne devrait pas dissuader de rédiger des tests.
Bernard
Si rien d'autre, cela indiquerait que le code n'a pas été écrit de manière défensive.
Robbie Dee
3

OK, je vais donner l'avis contraire ....

L'ajout de tests à un système de travail existant va modifier ce système, à moins que le système ne soit entièrement écrit de manière moqueuse dès le début. J'en doute, bien qu'il soit tout à fait possible qu'il dispose d'une bonne séparation de tous les composants avec des limites faciles à définir dans lesquelles vous pouvez glisser vos fausses interfaces. Mais si ce n'est pas le cas, vous devrez alors apporter des changements assez importants (relativement parlant) qui pourraient bien casser les choses. Dans le meilleur des cas, vous passerez beaucoup de temps à écrire ces tests, un temps qui pourrait être mieux consacré à la rédaction de documents de conception détaillés, d'analyses d'impact ou de documents de configuration de solutions. Après tout, c’est le travail que votre patron veut faire plus que des tests unitaires. N'est-ce pas?

En tout cas, je n’ajouterais aucun test unitaire.

Je me concentrerais sur des outils de test externes automatisés qui vous fourniraient une couverture raisonnable sans rien changer. Ensuite, lorsque vous apporterez des modifications ... vous pourrez alors commencer à ajouter des tests unitaires dans la base de code.

gbjbaanb
la source
2

J'ai souvent rencontré cette situation, hérité d'une base de code volumineuse sans couverture de test adéquate ou d'aucune couverture, et maintenant je suis responsable de l'ajout de fonctionnalités, de la correction de bugs, etc.

Mon conseil est de vérifier et de tester ce que vous ajoutez, et si vous corrigez des bogues ou modifiez les cas d'utilisation dans le code actuel, l'auteur teste ensuite. Si vous devez toucher quelque chose, écrivez des tests à ce moment-là.

Lorsque cela tombe en panne, c'est lorsque le code existant n'est pas bien structuré pour les tests unitaires. Vous passez donc beaucoup de temps à procéder à la refactorisation afin de pouvoir ajouter des tests pour les modifications mineures.

Casey
la source