Знущання з класу бетону - не рекомендується


11

Я щойно прочитав уривок книги "Зростаюче об'єктно-орієнтоване програмне забезпечення", в якій пояснюються деякі причини, чому знущатися з конкретного класу не рекомендується.

Ось приклад коду одиничного тесту для класу MusicCentre:

public class MusicCentreTest {
  @Test public void startsCdPlayerAtTimeRequested() {
    final MutableTime scheduledTime = new MutableTime();
    CdPlayer player = new CdPlayer() { 
      @Override 
      public void scheduleToStartAt(Time startTime) {
        scheduledTime.set(startTime);
      }
    }

    MusicCentre centre = new MusicCentre(player);
    centre.startMediaAt(LATER);

    assertEquals(LATER, scheduledTime.get());
  }
}

І його перше пояснення:

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

Я не можу точно зрозуміти, що він має на увазі, коли каже:

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

Я розумію, що сервіс відповідає MusicCentreметоду 'call' startMediaAt.

Що він має на увазі під «іншим місцем»?

Повний витяг тут: http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html


Додав коментар до свого блогу, оскільки я не зміг зрозуміти, що він має на увазі під цитатами.
олігофрен

@oligofren Це справді чудова загадка :) ...
Mik378

Відповіді:


6

Автор цієї публікації сприяє використанню інтерфейсів над використанням класів-членів.

It turns out that my MusicCentre object only uses the starting and stopping methods on the CdPlayer, the rest are used by some other part of the system. I'm over-specifying my MediaCentre by requiring it to talk to a CdPlayer, what it actually needs is a ScheduledDevice.

Відносини, які він турбується про повторне виявлення пізніше, полягає в тому, що класу MediaCentre не потрібен весь об'єкт CdPlayer. Його твердження полягає в тому, що, використовуючи Інтерфейс (імовірно, обмежений лише запуском | зупинки), простіше зрозуміти, що таке взаємодія насправді.

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

Претензія починає набувати більшого сенсу, коли ви виявляєте всі потенційні функції:

  • почати
  • Стоп
  • пауза
  • запис
  • випадковий порядок гри
  • зразки треків, початок пісні
  • зразкові композиції, випадковий зразок пісні
  • надавати інформацію про ЗМІ
  • ...

Тепер його твердження про "мені просто потрібно запустити і зупинити" має більше сенсу. Використання конкретного об'єкта-члена замість інтерфейсу робить майбутніми розробниками менш зрозумілими, що дійсно потрібно. Запуск тестів від MediaCentre на всіх інших функціях CdPlayer є марною витратою тестових зусиль, оскільки вони належать до стану "не байдуже". Якщо Recordфункція не працювала в цьому випадку, нас справді не хвилює, оскільки вона не потрібна. Але майбутній супровідник не обов'язково знатиме це на основі коду, як написано.

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


Дякую за цю чудову відповідь. Однак ви сказали: "Запуск тестів на всіх інших функціях є марною витратою тестування, оскільки вони належать до" не байдуже "стану." Хіба це не так: "Створення макетів для кожної з інших функцій є марною витратою тестування, оскільки вони належать до" не байдуже "стану."?
Mik378

@ Mik378 - так, саме так я і потрапив, я просто по-різному сформулював це. І я оновив свою відповідь, щоб зробити це більш зрозумілим.

Але я вважаю, що термін "запуск одиничних тестів" є заплутаним. Це означатиме, що MusicCentre збирається перевірити свій колектив ... тоді як насправді він MOCKS працює над тим, щоб перевірити свої власні послуги. До речі, зараз я розумію значення :)
Mik378

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

4

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

Думаючи про це багато, я отримую можливе тлумачення цієї цитати:

Процитована "послуга" відповідає "факту планування". Це може бути виражене добре названим та "зосередженим на одній ролі" інтерфейсом під назвою "ScheduledDevice" або вираженим неявно конкретною реалізацією методу, не залежно від будь-яких інтерфейсів.

У наведеному вище зразку планування виражається всім повнофункціональним об'єктом, названим CDPlayer. Таким чином, це все ще призводить до неявних взаємозв'язків між MusicCentreі "фактом планування".

Тож якщо ми почнемо вводити конкретні класи та знущатися над ними у об’єкти високого рівня; коли ми хочемо протестувати ці, ми повинні проаналізувати кожен введений "конкретний" об'єкт, щоб побачити, чи є він у певному взаємозв'язку, який ми маємо ЗРОБИТИ, оскільки вони приховані (неявні). Навпаки, кодування ЗАВЖДИ через інтерфейс дозволяє розробникові безпосередньо з'ясувати, яким відносинам повинен служити об'єкт високого рівня, і, отже, виявити особливості, з якими слід знущатися, щоб ізолювати блок-тест.


Я думаю, ти зараз це отримав. На жаль, я не отримав сповіщення про ваш коментар.
Стів Фрімен

3

Служба, яку я тут мав на увазі, був CDPlayer.scheduleToStartAt (). Саме так закликає MediaCentre - співпраця, який йому потрібен для функціонування. MediaCentre є об'єктом, що перевіряється.

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

Чи допомагає це?


(автор цієї чудової статті :)), що я хотів інтерпретувати, було це речення: "Це ускладнює зрозуміти, чи може служба, яка підтримує ці відносини, доречна в іншому місці, і мені доведеться робити аналіз ще раз, коли я працюю з класом ". Який аналіз? Факт виявлення того, який метод об'єкта повинен реалізувати зв'язок, оскільки цей чітко прихований?
Mik378
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.