Ecco il mio approccio. Ha un costo in termini di tempo perché è un test refactoring in 4 fasi.
Quello che ho intenzione di esporre può essere meglio disponibile in componenti con una complessità maggiore di quella esposta nell'esempio della domanda.
Ad ogni modo la strategia è valida per qualsiasi componente candidato ad essere normalizzato da un'interfaccia (DAO, Servizi, Controllori, ...).
1. L'interfaccia
Consente di raccogliere tutti i metodi pubblici da MyDocumentService e di raggrupparli 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 imponiamo MyDocumentService per implementare questa nuova interfaccia.
Fin qui tutto bene. Non sono stati apportati cambiamenti importanti, abbiamo rispettato l'attuale contratto e il comportamento rimane intatto.
public class MyDocumentService implements DocumentService {
@Override
public List<Document> getAllDocuments(){
//legacy code here as it is.
// with no changes ...
}
}
2. Test di unità del codice precedente
Qui abbiamo il duro lavoro. Per impostare una suite di test. Dovremmo stabilire il maggior numero di casi possibili: casi di successo e anche casi di errore. Questi ultimi sono per il bene della qualità del risultato.
Ora, invece di provare MyDocumentService useremo l'interfaccia come contratto da testare.
Non entrerò 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 ogni altra in questo approccio. Ed è il più importante perché imposterà il punto di riferimento per future comparazioni.
Nota: non sono state apportate modifiche importanti e il comportamento rimane inalterato. Suggerisco di fare un tag qui nel SCM. Tag o ramo non importa. Basta fare una versione.
Lo vogliamo per i rollback, le comparazioni delle versioni e potrebbe essere per le esecuzioni parallele del vecchio codice e del nuovo.
3. refactoring
Il Refactor sarà implementato in un nuovo componente. Non apporteremo alcuna modifica al codice esistente.
Il primo passo è facile come copiare e incollare MyDocumentService e rinominarlo in CustomDocumentService (ad esempio).
La nuova classe continua a implementare DocumentService . Quindi vai a rifattorizzare getAllDocuments () . (Iniziamo con uno. Pin-refactors)
Potrebbe richiedere alcune modifiche all'interfaccia / metodi di DAO. In tal caso, non modificare il codice esistente. Implementa il tuo metodo nell'interfaccia DAO. Annota il vecchio codice come Deprecato e saprai in seguito cosa rimuovere.
È importante non rompere / cambiare 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ù facile. 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 indipendentemente, ma possiamo anche confrontarci. Quest'ultima validazione è facoltativa e dipende dal risultato. Può essere che non è comparabile.
Non si può fare troppo 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 ...)
Note
Non oserei dire come eseguire i test unitari o come utilizzare le librerie simulate. Non oso né di dire come devi fare il refattore. Quello che volevo fare è suggerire una strategia globale. Come portarlo avanti dipende da te. Sai esattamente come è 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 con un servizio e vorrei seguire con DAO e così via. Andando in profondità nei livelli di dipendenza. Più o meno potrebbe essere descritto come una strategia up-bottom . Tuttavia, per modifiche / refatture minori ( come quello esposto nell'esempio del tour ), un bottom up farebbe più facilmente il compito. Perché l'ambito delle modifiche è poco.
Infine, spetta a te rimuovere il codice deprecato e reindirizzare le vecchie dipendenze a quello nuovo.
Rimuovi anche i test deprecati e il lavoro è finito. Se hai eseguito il versioning della vecchia soluzione con i suoi test, puoi controllarli e confrontarti in qualsiasi momento.
In conseguenza di così tanti lavori, hai testato il codice legacy, convalidato e versionato. E nuovo codice, testato, convalidato e pronto per essere versionato.