Aqui minha abordagem. Tem um custo em termos de tempo porque é um teste de refatoração em 4 fases.
O que vou expor pode ser melhor em componentes com mais complexidade do que o exposto no exemplo da pergunta.
De qualquer forma, a estratégia é válida para qualquer candidato componente a ser normalizado por uma interface (DAO, Serviços, Controladores, ...).
1. A interface
Vamos reunir todos os métodos públicos de MyDocumentService e vamos colocá-los todos juntos em uma interface. Por exemplo. Se já existir, use esse em vez de definir um novo .
public interface DocumentService {
List<Document> getAllDocuments();
//more methods here...
}
Em seguida, forçamos MyDocumentService a implementar essa nova interface.
Até aí tudo bem. Nenhuma mudança importante foi feita, nós respeitamos o contrato atual e os behaivos permanecem intocados.
public class MyDocumentService implements DocumentService {
@Override
public List<Document> getAllDocuments(){
//legacy code here as it is.
// with no changes ...
}
}
2. Teste de unidade de código legado
Aqui temos o trabalho duro. Para configurar um conjunto de testes. Devemos definir o maior número possível de casos: casos de sucesso e também casos de erro. Estes últimos são para o bem da qualidade do resultado.
Agora, em vez de testar MyDocumentService , vamos usar a interface como o contrato a ser testado.
Eu não vou entrar em detalhes, então me perdoe Se meu código parece muito simples ou muito 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
}
}
Esse estágio demora mais do que qualquer outro nessa abordagem. E é o mais importante porque definirá o ponto de referência para futuras comparações.
Nota: Devido a nenhuma grande mudança foi feita e o comportamento permanece intacto. Eu sugiro fazer uma tag aqui no SCM. Tag ou branch não importam. Basta fazer uma versão.
Nós queremos isso para rollbacks, comparações de versões e pode ser para execuções paralelas do código antigo e do novo.
3. Refatoração
[ Refator será implementado em um novo componente. Nós não faremos nenhuma alteração no código existente.
O primeiro passo é tão fácil quanto copiar e colar MyDocumentService e renomeá-lo para CustomDocumentService (por exemplo).
] Nova classe continua implementando o DocumentService . Em seguida, refatorize getAllDocuments () . (Vamos começar por um. Pin-refactors)
Isso pode exigir algumas alterações na interface / métodos do DAO. Em caso afirmativo, não altere o código existente. Implemente seu próprio método na interface do DAO. Anote o código antigo como Depreciado e você saberá mais tarde o que deve ser removido.
É importante não quebrar / alterar a implementação existente. Queremos executar os dois serviços em paralelo e comparar os 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. Atualizando o DocumentServiceTestSuite
Ok, agora é a parte mais fácil. Para adicionar os testes do novo 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);
}
}
Agora temos oldResult e newResult ambos validados independentemente, mas também podemos comparar um com o outro. Esta última validação é opcional e depende do resultado. Pode ser que não seja comparável.
Pode não ficar muito preocupado em comparar duas coleções dessa maneira, mas seria válido para qualquer outro tipo de objeto (pojos, entidades de modelo de dados, DTOs, Wrappers, tipos nativos ...)
Notas
Eu não ousaria dizer como fazer testes unitários ou como usar bibliotecas simuladas. Eu não ouso dizer como você tem que fazer o refatorador. O que eu queria fazer é sugerir uma estratégia global. Como levar isso para frente depende de você. Você sabe exatamente como o código é, sua complexidade e se essa estratégia vale a pena tentar. Fatos como tempo e recursos são importantes aqui. Também importa o que você espera desses testes no futuro.
Eu comecei meus exemplos por um serviço e gostaria de seguir com DAO e assim por diante. Indo profundamente nos níveis de dependência. Mais ou menos pode ser descrito como uma estratégia up-bottom . No entanto, para pequenas alterações / refatoradores ( como o exposto no exemplo da turnê ), um bottom up facilitaria a tarefa. Porque o escopo das mudanças é pequeno.
Por fim, cabe a você remover o código reprovado e redirecionar as dependências antigas para o novo.
Remova também os testes reprovados e o trabalho está concluído. Se você criou uma versão antiga da solução antiga com seus testes, é possível verificar e comparar um ao outro a qualquer momento.
Em consequência de tantos trabalhos, você tem código legado testado, validado e versionado. E novo código, testado, validado e pronto para ser versionado.