Écrire des tests pour du code dont je ne comprends pas le but

59

J'ai récemment terminé une refactorisation de la boîte noire. Je ne parviens pas à l'enregistrer car je ne sais pas comment le tester.

A un niveau élevé, j'ai une classe dont l'initialisation implique de récupérer des valeurs d'une classe B. Si la classe B est "vide", elle génère des valeurs par défaut sensibles. J'ai extrait cette partie à une méthode qui initialise la classe B avec ces mêmes valeurs par défaut.

Je n'ai pas encore déterminé le but / contexte de l'une ou l'autre classe, ni la manière dont ils seraient utilisés. Je ne peux donc pas initialiser l'objet à partir d'une classe B vide et vérifier qu'il a les bonnes valeurs / qu'il fait les choses correctement.

Ma meilleure idée est d'exécuter le code d'origine, de coder en dur dans les résultats des méthodes publiques en fonction des membres initialisés, et de tester le nouveau code par rapport à celui-ci. Je n'arrive pas à expliquer pourquoi je me sens vaguement mal à l'aise avec cette idée.

Y a-t-il une meilleure attaque ici?

JETM
la source
28
J'ai l'impression que vous avez commencé du mauvais côté. Vous devez d’abord comprendre le code, puis le tester, puis le refactoriser. Pourquoi refactorisez-vous sans savoir à quoi sert le code?
Jacob Raihle
11
@JacobRaihle C'est un programme plutôt spécialisé pour les personnes diplômées dans des domaines que je n'ai jamais touchés. Je saisis le contexte au fur et à mesure, mais il n’est tout simplement pas pratique d’attendre une bonne compréhension avant de commencer.
JETM
4
Ce qui n’est pas pratique, c’est de réécrire les choses et, lorsque les modifications sont en cours de production, de découvrir pourquoi vous n’auriez pas dû. Si vous pouvez effectuer des tests approfondis avant cette date, d'accord, cela peut être un bon moyen de connaître la base de code. Sinon, il est impératif que vous compreniez avant de changer.
Jacob Raihle
37
Il existe un type de test spécifique appelé test de caractérisation lorsque vous souhaitez tester le comportement réel du système. Vous prenez simplement votre système d'origine, puis ajoutez des tests qui affirment ce qu'il fait réellement (et pas nécessairement ce que vous vouliez faire!). Ils servent d’échafaudage autour de votre système, que vous pouvez modifier en toute sécurité puisque vous pouvez vous assurer qu’il conserve son comportement.
Vincent Savard
3
Ne pouvez - vous demander / l' obtenir examiné par quelqu'un qui fait comprendre?
pjc50

Réponses:

122

Vous le faites très bien!

La création de tests de régression automatisés est souvent la meilleure chose à faire pour rendre un composant refactorable. Cela peut paraître surprenant, mais de tels tests peuvent souvent être écrits sans une compréhension complète de ce que le composant fait en interne, pour autant que vous compreniez les "interfaces" d'entrée et de sortie (au sens général de ce mot). Nous avons fait cela plusieurs fois dans le passé pour des applications complètes, pas seulement pour des cours, mais cela nous a souvent permis d'éviter de casser des choses que nous ne comprenions pas bien.

Cependant, vous devez disposer de suffisamment de données de test et vous assurer de bien comprendre ce que le logiciel fait du point de vue de l'utilisateur de ce composant, sinon vous risqueriez d'omettre des cas de test importants.

C'est à mon humble avis une bonne idée d'implémenter vos tests automatisés avant de commencer la refactorisation, pas après. Vous pouvez donc effectuer la refactorisation par petites étapes et vérifier chaque étape. La refactorisation elle-même devrait rendre le code plus lisible, vous aidant ainsi à mieux comprendre les éléments internes peu à peu. Donc, les étapes de commande dans ce processus sont

  1. comprendre le code "de l'extérieur",
  2. écrire des tests de régression,
  3. refactor, ce qui conduit à une meilleure compréhension des éléments internes du code
Doc Brown
la source
21
Réponse parfaite, également telle que décrite dans le livre "Utilisation du code hérité"
Altoyr
Je devais faire quelque chose comme ça une fois. Recueillez des données de sortie typiques de l'application avant que je la modifie, puis vérifiez ma nouvelle version de l'application en exécutant les mêmes données de test. Il y a 30 ans ... Fortran ... C'était une sorte de traitement d'image / cartographie, donc je ne pouvais pas vraiment savoir ce que le résultat devrait être en le regardant ou en écrivant des cas de test. Et je l’ai fait sur un affichage vectoriel (persistant) Tektronix. Le gouvernement travaille ... 2 télétypes qui claquent derrière moi.
4
On pourrait ajouter que vous pouvez toujours écrire vos tests pour l’ancien code après coup. Ensuite, vous pouvez les essayer sur votre version refactorisée, et si cela se brise, faites une recherche de bissection dans votre historique de commit pour trouver le point où il commence à se rompre.
CodeMonkey
2
Je suggère de faire une dernière chose. Lors de la collecte des données de test, collectez des statistiques de couverture de code si possible. Vous saurez à quel point vos données de test décrivent le code en question.
Liori
2
@nocomprende, c'est drôle que j'ai fait la même chose avec un ancien code scientifique fortran 77 la semaine dernière. Ajoutez l’impression des données ASCII dans un fichier, configurez des répertoires de test avec les entrées et la sortie attendue. S'ils ne correspondent pas caractère pour caractère, j'ai cassé quelque chose. Lorsque le code est principalement constitué de deux sous-routines de 2 à 3 000 LdC chacune, vous devez commencer quelque part.
Godric Seer
1

L'une des principales raisons d'écrire des tests unitaires est qu'ils documentent l'API de composant d'une manière ou d'une autre. Ne pas comprendre le but du code testé est vraiment un problème ici. La couverture de code est un autre objectif important, difficile à atteindre sans savoir quelles branches d'exécution existent et comment elles sont déclenchées.

Cependant, s'il est possible de réinitialiser l'état proprement (ou de construire le nouvel objet de test à chaque fois), il est possible d'écrire un test de type "corbeille in-corbeille" qui alimente simplement le système principalement en entrée aléatoire et en observe la sortie.

Ces tests sont difficiles à maintenir car, s’ils échouent, il peut être complexe de dire pourquoi et comment il est grave. La couverture peut être discutable. Cependant, ils sont toujours beaucoup mieux que rien. Lorsqu'un tel test échoue, le développeur peut réviser les dernières modifications avec plus d'attention et, espérons-le, y détecter le bogue.

h22
la source
1
N'importe quel type d'information vaut mieux que de voler à l'aveugle. J'avais l'habitude de localiser des bogues dans les programmes serveur en production en appelant le débogueur sur un fichier de vidage sur incident (Unix) et en demandant la trace de la pile. Il m'a donné le nom de la fonction où la faute est survenue. Même sans autre connaissance (je ne savais pas comment utiliser ce débogueur), cela a contribué à créer une situation qui serait autrement mystérieuse et non reproductible.