Aquí mi acercamiento. Tiene un costo en términos de tiempo porque es una prueba de refactorización en 4 fases.
Lo que voy a exponer puede encajar mejor en componentes con más complejidad que el expuesto en el ejemplo de la pregunta.
De todos modos, la estrategia es válida para que cualquier candidato a componente sea normalizado por una interfaz (DAO, Servicios, Controladores, ...).
1. La interfaz
Vamos a reunir todos los métodos públicos de MyDocumentService y ponerlos todos juntos en una interfaz. Por ejemplo. Si ya existe, use ese en lugar de configurar uno nuevo .
public interface DocumentService {
List<Document> getAllDocuments();
//more methods here...
}
Luego forzamos a MyDocumentService a implementar esta nueva interfaz.
Hasta aquí todo bien. No se hicieron 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 un conjunto de pruebas. Deberíamos establecer tantos casos como sea posible: casos exitosos y también casos de error. Estos últimos son por el bien de la calidad del resultado.
Ahora, en lugar de probar MyDocumentService , vamos a utilizar la interfaz como el contrato que se va 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 permanece intacto. Sugiero hacer una etiqueta aquí en el SCM. La etiqueta o rama no importa. Solo haz una versión.
Lo queremos para retrocesos, 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).
Nueva clase sigue implementando DocumentService . Luego ve y refactoriza getAllDocuments () . (Comencemos por uno. Pin-refactores)
Puede requerir algunos cambios en la interfaz / métodos de DAO. Si es así, no cambie el código existente. Implemente su propio método en la interfaz DAO. Anote el código antiguo como obsoleto y sabrá más adelante 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 tenemos oldResult y newResult, ambos validados independientemente, pero también podemos comparar entre nosotros. Esta última validación es opcional y depende del resultado. Puede ser que no sea comparable.
Puede que no parezca demasiado comparar dos colecciones de esta manera, pero sería válido para cualquier otro tipo de objeto (pojos, entidades de modelo de datos, DTO, envoltorios, tipos nativos ...)
Notas
No me atrevería a decir cómo hacer pruebas unitarias o cómo usar libs simuladas. Tampoco me atrevo 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. Sabes exactamente cómo es el código, su complejidad y si vale la pena intentarlo. Hechos como el tiempo y los recursos son importantes aquí. También importa qué espera de estas pruebas en el futuro.
Comencé 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 una estrategia ascendente . Sin embargo, para cambios / refactores menores ( como el que se expone en el ejemplo del recorrido ), una tarea ascendente facilitaría la tarea. Porque el alcance de los cambios es pequeño.
Finalmente, depende de usted eliminar el código obsoleto y redirigir las dependencias antiguas al nuevo.
Elimine también las pruebas obsoletas 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, ha probado, validado y versionado el código heredado. Y nuevo código, probado, validado y listo para ser versionado.