Je suis actuellement en train de refactoriser une partie d'une grande base de code sans aucun test unitaire. J'ai essayé de refactoriser le code de manière brute, c'est-à-dire en essayant de deviner ce que fait le code et quels changements ne changeraient pas sa signification, mais sans succès: il casse au hasard les fonctionnalités tout autour de la base de code.
Notez que le refactoring comprend le déplacement du code C # hérité vers un style plus fonctionnel (le code hérité n'utilise aucune des fonctionnalités de .NET Framework 3 et versions ultérieures, y compris LINQ), l'ajout de génériques là où le code peut en bénéficier, etc.
Je ne peux pas utiliser de méthodes formelles , étant donné combien coûteraient-elles.
D'un autre côté, je suppose qu'au moins la règle "Tout code hérité refacturé doit être accompagné de tests unitaires" doit être strictement suivie, quel qu'en soit le coût. Le problème est que lorsque je refaçonne une petite partie d'une méthode privée de 500 LOC, l'ajout de tests unitaires semble être une tâche difficile.
Qu'est-ce qui peut m'aider à savoir quels tests unitaires sont pertinents pour un morceau de code donné? Je suppose que l'analyse statique du code serait en quelque sorte utile, mais quels sont les outils et les techniques que je peux utiliser pour:
Savoir exactement quels tests unitaires dois-je créer,
Et / ou savez-vous si le changement que j'ai effectué a affecté le code d'origine d'une manière qu'il s'exécute différemment à partir de maintenant?
la source
formal methods in software development
toute façon, car il est utilisé pour prouver l'exactitude d'un programme en utilisant une logique de prédicat et n'aurait pas d'application pour refactoriser une grande base de code. Méthodes formelles généralement utilisées pour prouver que le code fonctionne correctement dans des domaines tels que les applications médicales. Vous avez raison, il est coûteux de le faire, c'est pourquoi il n'est pas utilisé souvent.Réponses:
J'ai eu des défis similaires. Le livre Working with Legacy Code est une excellente ressource, mais il y a une supposition que vous pouvez faire du klaxon lors de tests unitaires pour soutenir votre travail. Parfois, ce n'est tout simplement pas possible.
Dans mon travail d'archéologie (mon terme pour la maintenance d'un code hérité comme celui-ci), je suis une approche similaire à ce que vous avez décrit.
À ce stade, vous devriez avoir une liste de candidats de ce qui a été exposé et / ou manipulé par cette routine. Certaines de ces manipulations sont susceptibles d'être involontaires. Maintenant, j'utilise
findstr
et l'IDE pour comprendre quels autres domaines peuvent référencer les éléments de la liste des candidats. Je vais passer un peu de temps à comprendre comment ces références fonctionnent et quelle est leur nature.Enfin, une fois que je me suis trompé en pensant que je comprends les impacts de la routine d'origine, je vais effectuer mes modifications une à la fois et réexécuter les étapes d'analyse que j'ai décrites ci-dessus pour vérifier que le changement fonctionne comme prévu. ça marche. J'essaie spécifiquement d'éviter de changer plusieurs choses à la fois car j'ai trouvé que cela explose sur moi lorsque j'essaie de vérifier l'impact. Parfois, vous pouvez vous en sortir avec plusieurs changements, mais si je peux suivre un itinéraire un par un, c'est ma préférence.
Bref, mon approche est similaire à celle que vous avez exposée. C'est beaucoup de travail de préparation; puis faites des changements individuels circonspects; puis vérifiez, vérifiez, vérifiez.
la source
Réponse courte: petits pas.
Considérez ces étapes:
Déplacez l'implémentation dans une fonction différente (privée) et déléguez l'appel.
Ajoutez le code de journalisation (assurez-vous que la journalisation n'échoue pas) dans votre fonction d'origine, pour toutes les entrées et sorties.
Exécutez votre application et faites tout ce que vous pouvez avec elle (utilisation valide, utilisation non valide, utilisation typique, utilisation atypique, etc.).
Vous avez maintenant des
max(call_count)
ensembles d'entrées et de sorties avec lesquels écrire vos tests; Vous pouvez écrire un seul test qui itère sur tous vos paramètres / jeux de résultats que vous avez et les exécute en boucle. Vous pouvez également écrire un test supplémentaire qui exécute une combinaison particulière (à utiliser pour vérifier rapidement le passage sur un ensemble d'E / S particulier).Déplacer de
// 500 LOC here
nouveau dans votreugly500loc
fonction (et supprimer une fonction de journalisation).Commencez à extraire les fonctions de la grande fonction (ne faites rien d'autre, extrayez simplement les fonctions) et exécutez les tests. Après cela, vous devriez avoir plus de petites fonctions à refactoriser, au lieu de 500LOC.
Vivre heureux pour toujours.
la source
Habituellement, les tests unitaires sont la voie à suivre.
Faites les tests nécessaires qui prouvent que le courant fonctionne comme prévu. Prenez votre temps et le dernier test doit vous faire confiance sur la sortie.
Vous êtes en train de refactoriser un morceau de code, vous devez savoir exactement ce qu'il fait et ce qu'il impacte. Donc, fondamentalement, vous devez tester toutes les zones touchées. Cela vous prendra beaucoup de temps ... mais c'est le résultat attendu de tout processus de refactoring.
Ensuite, vous pouvez tout déchirer sans aucun problème.
AFAIK, il n'y a pas de technique pare-balles pour cela ... il vous suffit d'être méthodique (quelle que soit la méthode sur laquelle vous vous sentez à l'aise), beaucoup de temps et beaucoup de patience! :)
Bravo et bonne chance!
Alex
la source