Здесь мой подход. Это имеет стоимость с точки зрения времени, потому что это рефактор-тест в 4 этапа.
То, что я собираюсь показать, может лучше подойти в компонентах с большей сложностью, чем в примере, приведенном в вопросе.
В любом случае стратегия действительна для любого кандидата компонента, который должен быть нормализован интерфейсом (DAO, Services, Controllers, ...).
1. Интерфейс
Давайте соберем все открытые методы из MyDocumentService и позволим объединить их все в интерфейс. Например. Если он уже существует, используйте его вместо установки нового .
public interface DocumentService {
List<Document> getAllDocuments();
//more methods here...
}
Затем мы заставляем MyDocumentService реализовать этот новый интерфейс.
Все идет нормально. Никаких серьезных изменений сделано не было, мы соблюдали действующий договор, и поведение остается неизменным.
public class MyDocumentService implements DocumentService {
@Override
public List<Document> getAllDocuments(){
//legacy code here as it is.
// with no changes ...
}
}
2. Модульное тестирование устаревшего кода
Здесь у нас тяжелая работа. Чтобы настроить тестовый набор. Мы должны установить как можно больше случаев: успешные случаи, а также ошибки. Это последнее во благо качества результата.
Теперь вместо тестирования MyDocumentService мы будем использовать интерфейс в качестве проверяемого контракта.
Я не буду вдаваться в подробности, так что прости меня, если мой код выглядит слишком простым или слишком агностичным
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
}
}
Этот этап занимает больше времени, чем любой другой в этом подходе. И это самое главное, потому что он будет служить ориентиром для будущих сравнений.
Примечание: из-за каких-либо серьезных изменений не было сделано, и поведение остается нетронутым. Я предлагаю сделать тег здесь в SCM. Тег или ветка не имеют значения. Просто сделай версию.
Мы хотим сделать это для отката, сравнения версий и, возможно, для параллельного выполнения старого кода и нового.
3. Рефакторинг
Рефакторинг будет внедрен в новый компонент. Мы не будем вносить никаких изменений в существующий код. Первый шаг так же прост, как скопировать и вставить MyDocumentService и переименовать его в CustomDocumentService (например).
Новый класс продолжает реализовывать DocumentService . Затем перейдите и выполните рефакторизацию getAllDocuments () . (Давайте начнем с одного. Pin-рефакторы)
Это может потребовать некоторых изменений в интерфейсе / методах DAO. Если это так, не меняйте существующий код. Реализуйте свой собственный метод в интерфейсе DAO. Аннотируйте старый код как устаревший, и позже вы узнаете, что следует удалить.
Важно не нарушать / не изменять существующую реализацию. Мы хотим выполнить оба сервиса параллельно, а затем сравнить результаты.
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. Обновление DocumentServiceTestSuite
Хорошо, теперь самая легкая часть. Добавить тесты нового компонента.
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);
}
}
Теперь у нас есть oldResult и newResult, которые проверены независимо, но мы также можем сравнивать друг с другом. Эта последняя проверка является необязательной и зависит от результата. Может быть, это не сравнимо.
Может не иметь большого смысла сравнивать две коллекции таким образом, но будет допустимо для любого другого типа объекта (pojos, сущности модели данных, DTO, Wrappers, нативные типы ...)
Примечания
Я не осмелился бы рассказать, как проводить юнит-тесты или как использовать фиктивные библиотеки. Я не смею ни говорить, как вы должны сделать рефакторинг. Я хотел предложить глобальную стратегию. Как это сделать, зависит от вас. Вы точно знаете, каков код, его сложность и стоит ли попробовать такую стратегию. Здесь важны такие факты, как время и ресурсы. Также имеет значение то, что вы ожидаете от этих тестов в будущем.
Я начал свои примеры с Службы, и я следовал бы с DAO и так далее. Углубляясь в уровни зависимости. Более или менее это может быть описано как восходящая стратегия. Тем не менее, для незначительных изменений / рефакторинга ( как тот, что показан в примере с туром ), восходящая задача облегчит задачу. Потому что масштаб изменений невелик.
Наконец, вы должны удалить устаревший код и перенаправить старые зависимости на новый.
Удалите также устаревшие тесты и работа сделана. Если вы версировали старое решение с его тестами, вы можете проверять и сравнивать друг друга в любое время.
В результате такого большого количества работы у вас есть унаследованный код, проверенный и проверенный. И новый код, проверенный, проверенный и готовый к версии.