Ось мій підхід. Він має витрату в часі, оскільки це тест на рефактор в 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. Рефакторинг
Refactor буде впроваджений у новий компонент. Ми не будемо змінювати існуючий код. Перший крок такий же простий, як зробити копіювання та вставлення MyDocumentService та перейменувати його у CustomDocumentService (наприклад).
Новий клас продовжує впроваджувати DocumentService . Потім перейдіть і рефакторизуйте getAllDocuments () . (Дозволяє запустити по одному. Pin-refactors)
Може знадобитися деякі зміни в інтерфейсі / методах 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 тощо. Заглиблюючись у рівні залежності. Більш-менш це можна було б описати як стратегію знизу . Однак для незначних змін / рефакторів ( як, наприклад, показаних у прикладі екскурсії ), знизу вгору полегшить цю задачу. Тому що сфера змін незначна.
Нарешті, від вас залежить видалення застарілого коду та перенаправлення старих залежностей на нову.
Видаліть також застарілі тести і робота виконана. Якщо ви впорядкували старе рішення з його тестами, ви можете будь-коли перевірити і порівняти один одного.
Як наслідок такої кількості робіт, у вас є перевірений, підтверджений та переосмислений застарілий код. І новий код, перевірений, перевірений і готовий до версії.