Aquí mi enfoque. Tiene un costo en términos de tiempo porque es una prueba de refactoría en 4 fases.
Lo que voy a exponer puede encajar mejor en componentes con más complejidad que la expuesta en el ejemplo de la pregunta.
De todos modos, la estrategia es válida para que cualquier componente candidato se normalice mediante una interfaz (DAO, Servicios, Controladores, ...).
1. La interfaz
Permite reunir todos los métodos públicos de MyDocumentService y los ponemos todos juntos en una interfaz. Por ejemplo. Si ya existe, use ese en lugar de establecer uno nuevo .
public interface DocumentService {
List<Document> getAllDocuments();
//more methods here...
}
Luego obligamos a MyDocumentService a implementar esta nueva interfaz.
Hasta ahora todo bien. No se realizaron cambios importantes, respetamos el contrato actual y behaivos permanece intacto.
public class MyDocumentService implements DocumentService {
@Override
public List<Document> getAllDocuments(){
//legacy code here as it is.
// with no changes ...
}
}
2. Prueba unitaria del código heredado
Aquí tenemos el trabajo duro. Para configurar una suite de prueba. Debemos establecer tantos casos como sea posible: casos exitosos y también casos de error. Estos últimos son para el bien de la calidad del resultado.
Ahora, en lugar de probar MyDocumentService , vamos a utilizar la interfaz como el contrato a probar.
No voy a entrar en detalles, así que perdóname si mi código parece demasiado simple o demasiado agnóstico
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
}
}
Esta etapa lleva más tiempo que cualquier otra en este enfoque. Y es lo más importante porque establecerá el punto de referencia para futuras comparaciones.
Nota: debido a que no se realizaron cambios importantes y el comportamiento se mantiene intacto. Sugiero hacer una etiqueta aquí en el SCM. Etiqueta o rama no importa. Solo haz una versión.
Lo queremos para reversiones, comparaciones de versiones y puede ser para ejecuciones paralelas del código antiguo y el nuevo.
3. Refactorización
Refactor se implementará en un nuevo componente. No haremos ningún cambio en el código existente.
El primer paso es tan fácil como copiar y pegar MyDocumentService y cambiarle el nombre a CustomDocumentService (por ejemplo).
La nueva clase sigue implementando DocumentService . Luego ve y refactoriza getAllDocuments () . (Comencemos por uno. Pin-refactors)
Puede requerir algunos cambios en la interfaz / métodos de DAO. Si es así, no cambie el código existente. Implementa tu propio método en la interfaz DAO. Anote el código antiguo como En desuso y más adelante sabrá qué debe eliminarse.
Es importante no romper / cambiar la implementación existente. Queremos ejecutar ambos servicios en paralelo y luego comparar los resultados.
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. Actualización de DocumentServiceTestSuite
Ok, ahora la parte más fácil. Para agregar las pruebas del nuevo 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);
}
}
Ahora hemos validado de forma independiente oldResult y newResult , pero también podemos compararnos entre nosotros. Esta última validación es opcional y depende del resultado. Puede que no sea comparable.
Puede que no haga demasiada cantidad para comparar dos colecciones de esta manera, pero sería válido para cualquier otro tipo de objeto (pojos, entidades de modelo de datos, DTO, envolturas, tipos nativos ...)
Notes
No me atrevería a decir cómo realizar pruebas unitarias o cómo utilizar las bibliotecas simuladas. No me atrevo ni a decir cómo tienes que hacer el refactor. Lo que quería hacer es sugerir una estrategia global. Cómo llevarlo adelante depende de ti. Usted sabe exactamente cómo es el código, su complejidad y si vale la pena probar esa estrategia. Hechos como el tiempo y los recursos son importantes aquí. También es importante lo que esperas de estas pruebas en el futuro.
He comenzado mis ejemplos por un Servicio y seguiría con DAO y así sucesivamente. Profundizando en los niveles de dependencia. Más o menos podría describirse como estrategia de abajo hacia arriba . Sin embargo, para cambios / refactores menores ( como el expuesto en el ejemplo de recorrido ), un de abajo hacia arriba haría la tarea más fácil. Debido a que el alcance de los cambios es pequeño.
Finalmente, depende de usted eliminar el código en desuso y redirigir las dependencias antiguas a la nueva.
Eliminar también las pruebas en desuso y el trabajo está hecho. Si versionó la solución anterior con sus pruebas, puede verificar y comparar entre sí en cualquier momento.
Como consecuencia de tanto trabajo, tiene un código heredado probado, validado y versionado. Y nuevo código, probado, validado y listo para ser versionado.