Ecco il mio approccio. Ha un costo in termini di tempo perché è un test di rifattore in 4 fasi.
Ciò che ho intenzione di esporre potrebbe essere migliore in componenti con una maggiore complessità rispetto a quello esposto nell'esempio della domanda.
Ad ogni modo la strategia è valida per qualsiasi componente candidato che deve essere normalizzato da un'interfaccia (DAO, Servizi, Controller, ...).
1. L'interfaccia
Consente di raccogliere tutti i metodi pubblici da MyDocumentService e di metterli tutti insieme in un'interfaccia. Per esempio. Se esiste già, usa quello invece di impostarne uno nuovo .
public interface DocumentService {
List<Document> getAllDocuments();
//more methods here...
}
Quindi forziamo MyDocumentService a implementare questa nuova interfaccia.
Fin qui tutto bene. Non sono state apportate modifiche sostanziali, abbiamo rispettato il contratto attuale e i comportamenti rimangono intatti.
public class MyDocumentService implements DocumentService {
@Override
public List<Document> getAllDocuments(){
//legacy code here as it is.
// with no changes ...
}
}
2. Unit test del codice legacy
Qui abbiamo il duro lavoro. Per impostare una suite di test. Dovremmo stabilire il maggior numero possibile di casi: casi di successo e anche casi di errore. Questi ultimi sono per il bene della qualità del risultato.
Ora, invece di testare MyDocumentService , utilizzeremo l'interfaccia come contratto da testare.
Non ho intenzione di entrare nei dettagli, quindi perdonami se il mio codice sembra troppo semplice o troppo agnostico
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
}
}
Questa fase richiede più tempo di qualsiasi altra in questo approccio. Ed è il più importante perché stabilirà il punto di riferimento per confronti futuri.
Nota: a causa della mancanza di modifiche sostanziali e il comportamento rimane intatto. Suggerisco di fare un tag qui in SCM. Tag o ramo non importa. Basta fare una versione.
Lo vogliamo per rollback, confronti di versioni e può essere per esecuzioni parallele del vecchio codice e di quello nuovo.
3. Refactoring
Refactor verrà implementato in un nuovo componente. Non faremo alcuna modifica al codice esistente. Il primo passaggio è semplice come copiare e incollare MyDocumentService e rinominarlo in CustomDocumentService (ad esempio).
La nuova classe continua a implementare DocumentService . Quindi vai e riformatta getAllDocuments () . (Iniziamo con uno. Rifattori di pin)
Potrebbe richiedere alcune modifiche all'interfaccia / ai metodi di DAO. In tal caso, non modificare il codice esistente. Implementa il tuo metodo nell'interfaccia DAO. Annota il vecchio codice come obsoleto e in seguito saprai cosa deve essere rimosso.
È importante non interrompere / modificare l'implementazione esistente. Vogliamo eseguire entrambi i servizi in parallelo e quindi confrontare i risultati.
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. Aggiornamento di DocumentServiceTestSuite
Ok, ora la parte più semplice. Per aggiungere i test del nuovo componente.
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);
}
}
Ora abbiamo oldResult e newResult entrambi validati in modo indipendente, ma possiamo anche confrontarli. Quest'ultima convalida è facoltativa e dipende dal risultato. Potrebbe non essere comparabile.
Potrebbe non essere troppo complicato per confrontare due raccolte in questo modo, ma sarebbe valida per qualsiasi altro tipo di oggetto (pojos, entità del modello di dati, DTO, wrapper, tipi nativi ...)
Appunti
Non oserei dire come fare unit test o come usare libs finti. Non oso nemmeno dire come devi fare il refactor. Quello che volevo fare è suggerire una strategia globale. Come portarlo avanti dipende da te. Sai esattamente com'è il codice, la sua complessità e se tale strategia merita di essere provata. Fatti come il tempo e le risorse contano qui. Importa anche cosa ti aspetti da questi test in futuro.
Ho iniziato i miei esempi da un servizio e seguirò con DAO e così via. Andare in profondità nei livelli di dipendenza. Più o meno potrebbe essere descritto come una strategia ascendente. Tuttavia per piccoli cambiamenti / refactor ( come quello esposto nell'esempio del tour ), un bottom-up renderebbe il compito più semplice. Perché l'ambito delle modifiche è piccolo.
Infine, spetta a te rimuovere il codice deprecato e reindirizzare le vecchie dipendenze a quello nuovo.
Rimuovi anche i test obsoleti e il lavoro viene eseguito. Se hai aggiornato la versione della vecchia soluzione con i relativi test, puoi controllarti e confrontarti in qualsiasi momento.
In conseguenza di così tanti lavori, hai testato, validato e aggiornato il codice legacy. E nuovo codice, testato, validato e pronto per essere aggiornato.