Яке призначення знущань над об’єктами?


167

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


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

2
можливий дублікат Що таке глузування?
nawfal

Відповіді:


360

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

Тестування одиниць

Уявіть тестування блоків для цієї системи:

cook <- waiter <- customer

Загалом легко передбачити тестування компонента низького рівня, як-от cook:

cook <- test driver

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

Складніше перевірити середній компонент, як офіціант, який використовує поведінку інших компонентів. Наївний тестер може перевірити компонент офіціанта так само, як ми перевірили компонент кухаря:

cook <- waiter <- test driver

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

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

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

    -----------------------
   |                       |
   v                       |
test cook <- waiter <- test driver

Тут тестовий кухар знаходиться "в змові" з драйвером. В ідеалі система, що випробовується, розроблена таким чином, щоб тестовий кухар міг легко бути заміненим ( введений для ін'єкцій) ) для роботи з офіціантом без зміни виробничого коду (наприклад, без зміни коду офіціанта).

Знущаються над об’єктами

Тепер тестовий кухар (тестовий подвійний) може бути реалізований різними способами:

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

Дивіться статтю Фаулера для більш детальної інформації про підробки проти заглушок проти макетів проти манекенів , але поки що зупинимось на макет-кухарі.

    -----------------------
   |                       |
   v                       |
mock cook <- waiter <- test driver

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

Об'єкт макету заздалегідь знає, що має відбутися під час тестування (наприклад, який із його методів буде викликано дзвінки тощо), і об'єкт макету знає, як він повинен реагувати (наприклад, яке повернене значення надати). Знущання вказуватиме, чи відрізняється те, що насправді відбувається від того, що повинно відбутися. Спеціальний макетний об'єкт може бути створений з нуля для кожного тестового випадку для виконання очікуваної поведінки для цього тестового випадку, але глузуючий каркас прагне дозволити чітко та легко вказати таку специфікацію поведінки у тестовому випадку.

Розмова навколо макетного тесту може виглядати так:

тест-драйвер, щоб знущатися над кухарем : очікуйте, що хот-дог замовить, і дайте йому цю манекену гарячу собаку у відповідь

тест-драйвер (видається замовником) офіціанту : я хотів би хот-дога, будь ласка
офіціант , щоб знущатися готувати : 1 хот - дог , будь ласка ,
макет готувати до офіціантові : замовлення до: 1 хот - дог готовий (дає фіктивний хот - дог на офіціанта)
офіціантів , щоб водій - випробувач : ось ваш хот-дог (дає манекена хот-дога для тест-драйвера)

тест-драйвер : ТЕСТУВАННЯ!

Але оскільки наш офіціант новий, це може статися:

тест-драйвер, щоб знущатися над кухарем : очікуйте, що хот-дог замовить, і дайте йому цю манекену гарячу собаку у відповідь

тест-драйвер (виступає замовником) офіціанту : Я хотів хот - дог , будь ласка ,
офіціанта , щоб знущатися готувати : 1 гамбургер , будь ласка ,
знущатися кухар зупиняє тест: Мені сказали , що очікувати гарячий замовлення собачий!

тест-драйвер відзначає проблему: TEST FAILED! - офіціант змінив наказ

або

тест-драйвер, щоб знущатися над кухарем : очікуйте, що хот-дог замовить, і дайте йому цю манекену гарячу собаку у відповідь

водій - випробувач (видаючи себе за клієнта) до офіціантові : Я хотів хот - дог , будь ласка ,
офіціанта , щоб знущатися готувати : 1 хот - дог , будь ласка
макет кухар для офіціанта : замовлення до: 1 хот - дог готовий (дає фіктивний хот - дог на офіціанта)
офіціантів , щоб водій - випробувач : ось ваш картопля фрі (дає картоплю фрі з іншого замовлення на тест-драйвер)

тест-драйвер відзначає несподівані картопля фрі: TEST FAILED! офіціант повернув неправильну страву

Можливо, важко зрозуміти різницю між макетними об'єктами та заглушками без контрастного прикладу на основі заглушки, щоб піти з цим, але ця відповідь занадто довга :-)

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


12
Це чудове пояснення, але чи не ви певною мірою тестуєте впровадження офіціанта? У вашому випадку це, мабуть, добре, тому що ви перевіряєте, чи він використовує правильний API, але що робити, якщо є різні способи зробити це, і офіціант може вибрати той чи інший? Я подумав, що сенс тестування блоку полягає у тестуванні API, а не в реалізації. (Це питання, яке я завжди задаю собі, коли читаю про глузування.)
davidtbernal

8
Дякую. Я не можу сказати, чи ми тестуємо "реалізацію", не побачивши (або визначивши) специфікацію для офіціанта. Ви можете припустити, що офіціанту дозволяється готувати страву самостійно або заповнювати замовлення по вулиці, але я припускаю, що специфікація для офіціанта включає використання призначеного шеф-кухаря - адже шеф-кухар виробництва - це дорогий шеф-кухар, і ми ' буду віддавати перевагу нашому офіціанту. Без цієї специфікації, мабуть, я повинен був би зробити висновок, що ти маєш рацію - офіціант може заповнити замовлення, але хоче, щоб бути "правильним". OTOH, без специфікації, тест є безглуздим. [Продовження ...]
Берт F

8
НІКОЛИ, ви робите чудовий момент, який веде до фантастичної теми тестування блоків білого та проти чорного поля. Я не думаю, що існує галузевий консенсус, який говорить, що тестування одиниць повинно бути чорним вікном, а не білим вікном ("перевірити API, а не реалізацію"). Я думаю, що найкращим тестуванням модуля, ймовірно, повинно бути поєднання двох, щоб збалансувати крихкість тесту проти покриття коду та повноти тестового випадку.
Берт F

1
На мене ця відповідь недостатньо технічна. Я хочу знати, чому я повинен використовувати макетний об’єкт, коли я можу використовувати реальні об'єкти.
Ніклас Р.

1
Чудове пояснення !! Спасибі!! @BertF
Бхарат Муралі

28

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

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

http://en.wikipedia.org/wiki/Mock_object

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

Слово «Макет» іноді помилково вживається взаємозамінно зі словом «Стуб». Тут описані відмінності між двома словами . По суті, макет - це об'єкт-заглушка, який також включає очікування (тобто "твердження") щодо правильної поведінки тестуваного об'єкта / методу.

Наприклад:

class OrderInteractionTester...
  public void testOrderSendsMailIfUnfilled() {
    Order order = new Order(TALISKER, 51);
    Mock warehouse = mock(Warehouse.class);
    Mock mailer = mock(MailService.class);
    order.setMailer((MailService) mailer.proxy());

    mailer.expects(once()).method("send");
    warehouse.expects(once()).method("hasInventory")
      .withAnyArguments()
      .will(returnValue(false));

    order.fill((Warehouse) warehouse.proxy());
  }
}

Зауважте, що об'єкти warehouseта mailerмакет запрограмовані на очікувані результати.


2
Визначення, яке ви надали, відрізняється не в останню чергу від "об'єкта заглушки", і як таке не пояснює, що таке макетний об'єкт.
Brent Arias

Інша корекція "слово" глузування "іноді помилково вживається взаємозамінно із" заглушкою "".
Brent Arias

@Myst: Використання двох слів не є універсальним; вона різниться між авторами. Фаулер говорить так, і стаття у Вікіпедії так говорить. Однак не соромтесь редагувати зміни та вилучити свій внесок. :)
Роберт Харві

1
Я погоджуюся з Робертом: вживання слова "макет" має тенденцію до різної залежності від галузі, але не існує встановленого визначення відповідно до мого досвіду, за винятком того, що його, як правило, НЕ є фактично об'єктом, що тестується, а існує для полегшення тестування, коли використовується фактичне предмет або всі його частини були б дуже незручними і мало наслідком.
mkelley33

15

Об'єкти макету - це імітовані об’єкти, що імітують поведінку реальних. Зазвичай ви пишете макетний об'єкт, якщо:

  • Реальний об'єкт занадто складний, щоб включити його в тестування одиниць (наприклад, мережеве спілкування, ви можете мати макетний об'єкт, який імітує інший аналог)
  • Результат вашого об'єкта не детермінований
  • Справжній об’єкт ще не доступний

12

Об'єкт Mock - це один із видів тестового подвійного . Ви використовуєте mockobjects для тестування та перевірки протоколу / взаємодії тестуваного класу з іншими класами.

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

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

давайте подумаємо, що повинна робити послуга: вона повинна отримати віджет із бази даних, зробити щось із цим і зберегти його знову.

Отже, у псевдомові з бібліотекою псевдо-макетів у нас вийде щось на кшталт:

Widget sampleWidget = new Widget();
WidgetDao mock = createMock(WidgetDao.class);
WidgetService svc = new WidgetService(mock);

// record expected calls on the dao
expect(mock.getById(id)).andReturn(sampleWidget);   
expect(mock.save(sampleWidget);

// turn the dao in replay mode
replay(mock);

svc.updateWidgetPrice(id,newPrice);

verify(mock);    // verify the expected calls were made
assertEquals(newPrice,sampleWidget.getPrice());

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


11

Я настійно рекомендую чудову статтю Мартіна Фаулера, яка пояснює, що саме є макетами та чим вони відрізняються від заглушок.


10
Не зовсім для початківців, чи не так?
Роберт Харві

@Robert Harvey: Можливо, все одно добре бачити, що це було корисно для уточнення вашої відповіді :)
Adam Byrtek

Статті Мартіна Фаулера написані в образі RFC: сухе та холодне.
revo

9

Під час тестування певної частини комп'ютерної програми в ідеалі ви хочете перевірити саме поведінку саме цієї частини.

Наприклад, подивіться на псевдо-код нижче з уявного фрагмента програми, яка використовує іншу програму для виклику надрукувати щось:

If theUserIsFred then
    Call Printer(HelloFred)
Else
   Call Printer(YouAreNotFred)
End

Якщо ви тестували це, ви хотіли б протестувати частину, на яку дивиться, користувач Фред чи ні. Ви не дуже хочете перевіряти Printerчастину речей. Це було б ще одне випробування.

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


Існує кілька інших типів об'єктів, які можна робити, і ви не можете використовувати Mocks. Головне, що робить Mocks Mocks - це те, що вони можуть бути налаштовані на поведінку та очікування.

Очікування дозволяють вашому Mock викликати помилку при неправильному використанні. Отже, у наведеному вище прикладі ви можете бути впевнені, що принтер викликається з HelloFred у тестовому випадку "користувач є Фред". Якщо цього не відбудеться, ваш Mock може попередити вас.

Поведінка в Mocks означає, що, наприклад, ваш код зробив щось на кшталт:

If Call Printer(HelloFred) Returned SaidHello Then
    Do Something
End

Тепер ви хочете перевірити, що робить ваш код, коли принтер викликається і повертається SaidHello, так що ви можете встановити Mock для повернення SaidHello, коли він викликається разом з HelloFred.

Одним із хороших ресурсів для цього є пост Мартіна Фаулерса Mocks Aren't Stubs


7

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

Коротше кажучи, ви використовуєте заглушки, щоб розбити залежність SUT (System Under Test) від інших об'єктів і знущаються, щоб зробити це і перевірити, чи SUT викликав певні методи / властивості залежно. Це повертається до основних принципів одиничного тестування - тести повинні бути легко читабельними, швидкими і не потребують конфігурації, що може означати використання всіх реальних класів.

Як правило, у вашому тесті може бути більше однієї заглушки, але ви повинні мати лише один макет. Це тому, що мета макету - перевірити поведінку, і ваш тест повинен перевірити лише одне.

Простий сценарій з використанням C # та Moq:

public interface IInput {
  object Read();
}
public interface IOutput {
  void Write(object data);
}

class SUT {
  IInput input;
  IOutput output;

  public SUT (IInput input, IOutput output) {
    this.input = input;
    this.output = output;
  }

  void ReadAndWrite() { 
    var data = input.Read();
    output.Write(data);
  }
}

[TestMethod]
public void ReadAndWriteShouldWriteSameObjectAsRead() {
  //we want to verify that SUT writes to the output interface
  //input is a stub, since we don't record any expectations
  Mock<IInput> input = new Mock<IInput>();
  //output is a mock, because we want to verify some behavior on it.
  Mock<IOutput> output = new Mock<IOutput>();

  var data = new object();
  input.Setup(i=>i.Read()).Returns(data);

  var sut = new SUT(input.Object, output.Object);
  //calling verify on a mock object makes the object a mock, with respect to method being verified.
  output.Verify(o=>o.Write(data));
}

У наведеному вище прикладі я використовував Moq для демонстрації заглушок і макетів. Moq використовує той самий клас для обох - Mock<T>що робить його трохи заплутаним. Незважаючи на те, під час виконання тестування вийде з ладу, якщо output.Writeйого не викликають з даними як parameter, тоді як невдалий виклик input.Read()не вийде з ладу.


4

Як ще одна відповідь запропонована за посиланням на " Знущання не заглушки ", макети є формою "тестового подвійного", що використовується замість реального об'єкта. Те, що їх відрізняє від інших форм тестових пар, таких як об'єкти-заглушки, полягає в тому, що інші тестові парні пропонують перевірку стану (і, можливо, моделювання), тоді як знущаються пропонують перевірку поведінки (і необов'язково моделювання).

За допомогою заглушки ви можете зателефонувати декількома методами на заглушці в будь-якому порядку (або навіть повторно) і визначити успіх, якщо заглушка набула значення або стан, який ви намітили. Навпаки, макетний об’єкт очікує виклику дуже специфічних функцій у певному порядку та навіть певній кількості разів. Тест з макетним об’єктом буде вважатися "невдалим" просто тому, що методи викликалися в іншій послідовності або рахунку - навіть якщо об'єкт макету мав правильний стан при завершенні тесту!

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


3

Частина сенсу використання макетних об'єктів полягає в тому, що їх не потрібно реально реалізовувати відповідно до специфікації. Вони можуть просто дати відповіді на манекени. Наприклад, якщо вам доведеться реалізувати компоненти A і B і обидва "дзвонити" (взаємодіяти) один з одним, ви не можете перевірити A, поки B не буде реалізовано, і навпаки. У тестовій розробці це проблема. Таким чином, ви створюєте макетні ("манекени") об'єкти для A і B, які дуже прості, але вони дають певну відповідь при взаємодії з ними. Таким чином, ви можете реалізувати та протестувати A, використовуючи макетний об’єкт для B.


1

Для php та phpunit добре пояснено у документації phpunit. дивіться тут документацію phpunit

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


0

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.