Voici mon approche. Cela a un coût en termes de temps car il s'agit d'un test de refactorisation en 4 phases.
Ce que je vais exposer convient peut-être mieux aux composants plus complexes que celui exposé dans l'exemple de la question.
Quoi qu'il en soit, la stratégie est valable pour tout composant candidat à normaliser par une interface (DAO, Services, Contrôleurs, ...).
1. l'interface
Permet de rassembler toutes les méthodes publiques de MyDocumentService et de les rassembler dans une interface. Par exemple. S'il existe déjà, utilisez celui-là au lieu d'en créer un nouveau .
public interface DocumentService {
List<Document> getAllDocuments();
//more methods here...
}
Ensuite, nous forçons MyDocumentService à implémenter cette nouvelle interface.
Jusqu'ici tout va bien. Aucun changement majeur n'a été apporté, nous avons respecté le contrat actuel et behaivos reste intact.
public class MyDocumentService implements DocumentService {
@Override
public List<Document> getAllDocuments(){
//legacy code here as it is.
// with no changes ...
}
}
2. Test unitaire du code hérité
Ici nous avons le travail difficile. Pour configurer une suite de tests. Nous devrions définir autant de cas que possible: des cas réussis mais aussi des cas d'erreur. Ces dernières sont pour le bien de la qualité du résultat.
Au lieu de tester MyDocumentService, nous allons utiliser l'interface comme contrat à tester.
Je ne vais pas entrer dans les détails, alors pardonnez-moi si mon code est trop simple ou trop agnostique
public class DocumentServiceTestSuite {
@Mock
MyDependencyA mockDepA;
@Mock
MyDependencyB mockDepB;
//... More mocks
DocumentService service;
@Before
public void initService(){
service = MyDocumentService(mockDepA, mockDepB);
//this is purposed way to inject
//dependencies. Replace it with one you like more.
}
@Test
public void getAllDocumentsOK(){
// here I mock depA and depB
// wanted behaivors...
List<Document> result = service.getAllDocuments();
Assert.assertX(result);
Assert.assertY(result);
//... As many you think appropiate
}
}
Cette étape prend plus de temps que toute autre dans cette approche. Et c'est le plus important, car cela servira de point de référence pour les comparaisons futures.
Remarque: En raison de l'absence de modifications majeures, behaivor reste inchangé. Je suggère de faire une étiquette ici dans le SCM. Le tag ou la branche n'a pas d'importance. Il suffit de faire une version.
Nous le voulons pour les annulations, les comparaisons de versions et peut être pour les exécutions en parallèle de l'ancien code et du nouveau.
3. Refactoring
Refactor va être implémenté dans un nouveau composant. Nous ne ferons aucune modification sur le code existant. La première étape est aussi simple que de copier-coller de MyDocumentService et de le renommer en CustomDocumentService (par exemple).
La nouvelle classe continue d'implémenter DocumentService . Puis allez et refactorisez getAllDocuments () . (Commençons par un. Pin-refactors)
Cela peut nécessiter quelques modifications sur l'interface / les méthodes de DAO. Si c'est le cas, ne changez pas le code existant. Implémentez votre propre méthode dans l'interface DAO. Annotez l'ancien code avec obsolète et vous saurez plus tard ce qui doit être supprimé.
Il est important de ne pas casser / changer la mise en œuvre existante. Nous voulons exécuter les deux services en parallèle, puis comparer les résultats.
public class CustomDocumentService implements DocumentService {
@Override
public List<Document> getAllDocuments(){
//new code here ...
//due to im refactoring service
//I do the less changes possible on its dependencies (DAO).
//these changes will come later
//and they will have their own tests
}
}
4. Mise à jour de DocumentServiceTestSuite
Ok, maintenant la partie la plus facile. Ajouter les tests du nouveau composant.
public class DocumentServiceTestSuite {
@Mock
MyDependencyA mockDepA;
@Mock
MyDependencyB mockDepB;
DocumentService service;
DocumentService customService;
@Before
public void initService(){
service = MyDocumentService(mockDepA, mockDepB);
customService = CustomDocumentService(mockDepA, mockDepB);
// this is purposed way to inject
//dependencies. Replace it with the one you like more
}
@Test
public void getAllDocumentsOK(){
// here I mock depA and depB
// wanted behaivors...
List<Document> oldResult = service.getAllDocuments();
Assert.assertX(oldResult);
Assert.assertY(oldResult);
//... As many you think appropiate
List<Document> newResult = customService.getAllDocuments();
Assert.assertX(newResult);
Assert.assertY(newResult);
//... The very same made to oldResult
//this is optional
Assert.assertEquals(oldResult,newResult);
}
}
Nous avons maintenant oldResult et newResult validés indépendamment, mais nous pouvons également comparer les uns avec les autres. Cette dernière validation est facultative et dépend du résultat. Peut-être que ce n'est pas comparable.
Ne comparez peut-être pas deux collections de cette façon, mais serait valable pour tout autre type d’objet (pojos, entités de modèle de données, DTO, wrappers, types natifs, etc.).
Remarques
Je n'oserais pas dire comment faire des tests unitaires ou comment utiliser des librairies factices. Je n'ose pas non plus dire comment vous devez faire le refactor. Ce que je voulais faire, c'est suggérer une stratégie globale. Comment aller de l'avant dépend de vous. Vous savez exactement ce qu'est le code, sa complexité et si une telle stratégie vaut la peine d'être essayée. Des faits tels que le temps et les ressources sont importants ici. En outre, qu'attendez-vous de ces tests à l'avenir?
J'ai commencé mes exemples par un service et je suivrais avec DAO et ainsi de suite. Aller au fond des niveaux de dépendance. Plus ou moins, cela pourrait être décrit comme une stratégie ascendante . Cependant, pour les modifications / refactorisations mineures ( comme celle exposée dans l'exemple de tournée ), un processus ascendant faciliterait la tâche. Parce que la portée des changements est faible.
Enfin, il vous appartient de supprimer le code obsolète et de rediriger les anciennes dépendances vers la nouvelle.
Supprimez également les tests obsolètes et le travail est terminé. Si vous avez mis à niveau l'ancienne solution avec ses tests, vous pouvez vérifier et comparer à tout moment.
En conséquence de tant de travail, vous avez testé, validé et mis à jour le code existant. Et un nouveau code, testé, validé et prêt à être mis en version.