Поступові підходи до ін'єкції залежності


12

Я працюю над тим, щоб зробити свої класи одиничними для перевірки, використовуючи ін'єкцію залежності. Але в деяких з цих класів є багато клієнтів, і я ще не готовий рефакторировать їх усіх, щоб почати проходити залежність. Тому я намагаюся робити це поступово; збереження залежностей за замовчуванням на даний момент, але дозволяє їх замінити для тестування.

Один із підходів, який я маю на увазі, - це просто переміщення всіх "нових" викликів у власні методи, наприклад:

public MyObject createMyObject(args) {
  return new MyObject(args);
}

Тоді в моїх тестових одиницях я можу просто підкласифікувати цей клас і замінити функції створення, тому вони замість цього створюють підроблені об'єкти.

Це хороший підхід? Чи є недоліки?

Загалом, чи добре мати жорсткі залежності, якщо ви можете замінити їх для тестування? Я знаю, що кращий підхід полягає в тому, щоб явно вимагати їх у конструкторі, і я хотів би потрапити туди врешті. Але мені цікаво, чи це хороший перший крок.

Один недолік, який мені щойно стався: якщо у вас є реальні підкласи, які потрібно перевірити, ви не можете повторно використовувати тестовий підклас, який ви написали для батьківського класу. Вам доведеться створити тестовий підклас для кожного реального підкласу, і він повинен буде замінити однакові функції створення.


Чи можете ви детальніше пояснити, чому вони зараз не перевіряються на одиницю?
Остін Салонен

Існує багато "нового X", розпорошеного по коду, а також багато застосувань статичних класів та одинаків. Тому коли я намагаюся протестувати один клас, я справді випробовую цілу купу їх. Якщо я зможу виділити залежності та замінити їх фальшивими об'єктами, я матиму більше контролю над тестуванням.
JW01

Відповіді:


13

Це хороший підхід для початку роботи. Зауважте, що найважливішим є покриття наявного коду тестовими одиницями; Після того, як ви проведете тести, ви зможете рефакторировать вільніше, щоб вдосконалити дизайн.

Тож початковий пункт - не робити дизайн елегантним та блискучим - просто зробити блок коду перевіряючим з найменшими ризиковими змінами . Без одиничних тестів ви повинні бути надзвичайно консервативними та обережними, щоб уникнути порушення коду. Ці початкові зміни можуть навіть змусити код виглядати більш незграбним або потворним у деяких випадках - але якщо вони дозволять вам написати тести першого блоку, врешті-решт ви зможете перейти до ідеального дизайну, який ви маєте на увазі.

Основна робота, яку слід прочитати на цю тему, - це ефективно працювати зі спадковим кодексом . Він описує трюк, який ви показуєте вище, і багато, багато іншого, використовуючи кілька мов.


Лише зауваження до цього "щойно пройдете тести, ви можете рефакторувати вільніше" - Якщо у вас немає тестів, ви не рефакторинг, ви просто змінюєте код.
ocodo

@Slomojo Я бачу вашу думку, але ви все ще можете зробити багато безпечного рефакторингу без тестів, особливо автоматизованих IDE-ректорів, таких як, наприклад, (у затемненні для Java), витягування дублікату коду до методів, перейменування всього, переміщення класів та вилучення полів, констант пр.
Альб

Я справді просто цитую витоки терміна Refactoring, який є модифікацією коду на вдосконалені зразки, захищені від регресійних помилок тестовою рамкою. Звичайно, рефакторинг на основі IDE зробив його набагато тривіальнішим до програмного забезпечення «рефактор», але я, як правило, вважаю за краще уникати самозабладнання, якщо в моєму програмному забезпеченні немає тестів, і я «рефактор» я просто змінюю код. Звичайно, це може бути не те, що я кажу комусь іншому;)
ocodo

1
@Alb, рефакторинг без тестів завжди більш ризикований. Автоматизовані рефактори визначають цей ризик, але не до нуля. Завжди є складні випадки, з якими інструменти можуть не впоратися правильно. У кращому випадку результат є деяким повідомленням про помилку від інструменту, але в гіршому випадку він може мовчки ввести непомітні помилки. Тож радимо дотримуватися найпростіших і безпечних можливих змін навіть за допомогою автоматизованих інструментів.
Péter Török

3

Люди з Guice рекомендують використовувати

@Inject
public void setX(.... X x) {
   this.x = x;
}

для всіх властивостей, а не просто додавання @Inject до їх визначення. Це дозволить вам ставитися до них як до звичайних бобів, що ви можете newпід час тестування (та встановлення властивостей вручну) та @Inject у виробництво.


3

Коли я зіткнувся з таким типом проблеми, я визначу кожну залежність свого тестування класів і створя захищений конструктор, який дозволить мені передавати їх. Це фактично те саме, що створити сеттер для кожного, але я віддаю перевагу декларуючи залежності в моїх конструкторах, щоб я не забув створити їх у майбутньому.

Тож мій новий тестовий клас одиниць матиме 2 конструктори:

public MyClass() { 
  this(new Client1(), new Client2()); 
}

public MyClass(Client1 client1, Client2 client2) { 
  this.client1 = client1; 
  this.client2 = client2; 
}

2

Ви можете розглянути ідіому "гетьтер створює", поки не зможете використовувати введену залежність.

Наприклад,

public class Example {
  private MyObject myObject=null;

  public MyObject getMyObject() {
    if (myObject==null) {
      myObject=new MyObject();
    } 
    return myObject;
  }

  public void setMyObject(MyObject myObject) {
    this.myObject=myObject;
  }

  private void myMethod() {
    if (getMyObject().doSomething()) {
      // Instance automatically created
    }
  }
}

Це дозволить вам рефакторировать внутрішньо, щоб ви могли посилатися на мій об'єкт через геттер. Вам ніколи не потрібно дзвонити new. Надалі, коли всі клієнти налаштовані на дозвіл введення залежності, тоді все, що вам потрібно зробити, - це видалити код створення в одному місці - геттера. Установник дозволить вам вводити макетні об’єкти за потребою.

Вищевказаний код є лише прикладом, і він безпосередньо піддається внутрішньому стану. Ви повинні уважно розглянути, чи підходить цей підхід для вашої бази даних.

Я також рекомендую вам прочитати " Ефективна робота зі спадковим кодом ", яка містить безліч корисних порад, щоб досягти успіху у великому рефакторингу.


Дякую. Я розглядаю такий підхід для деяких випадків. Але що робити, коли конструктор MyObject вимагає параметрів, доступних лише всередині вашого класу? Або коли вам потрібно створити більше одного MyObject протягом життя вашого класу? Чи потрібно вводити MyObjectFactory?
JW01

@ JW01 Ви можете налаштувати код творця getter так, щоб читати внутрішнє стан свого класу та просто підходити до нього. Створення декількох екземплярів протягом життя буде складно оркеструвати. Якщо ви зіткнетеся з цією ситуацією, я б запропонував перепроектувати, а не пообіцяти. Не робіть своїх загороджувачів занадто складними, інакше вони стануть жорнами на шиї. Ваша мета - полегшити процес рефакторингу. Якщо це здається занадто важким, перемістіться в іншу область, щоб там виправити.
Гері Роу

це синглтон. Просто не використовуйте
одинаків O_O

@DarioOO Насправді це дуже бідний сингл. Для кращої реалізації було б використати перерахунок, як за Джошем Блохом. Синглтони - це дійсна модель дизайну та їх використання (дороге одноразове створення тощо).
Gary Rowe
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.