Я новачок в одиничному тестуванні, і я невпинно чую, як слова "знущаються над предметами" кидаються навколо. Зрозуміло, може хтось пояснить, що таке макетні об'єкти і для чого вони зазвичай використовуються при написанні одиничних тестів?
Я новачок в одиничному тестуванні, і я невпинно чую, як слова "знущаються над предметами" кидаються навколо. Зрозуміло, може хтось пояснить, що таке макетні об'єкти і для чого вони зазвичай використовуються при написанні одиничних тестів?
Відповіді:
Оскільки ви говорите, що ви новачок у тестуванні одиниць і просили знущатися над предметами в "умовах мирян", я спробую приклад неспеціаліста.
Уявіть тестування блоків для цієї системи:
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! офіціант повернув неправильну страву
Можливо, важко зрозуміти різницю між макетними об'єктами та заглушками без контрастного прикладу на основі заглушки, щоб піти з цим, але ця відповідь занадто довга :-)
Також зауважте, що це досить спрощений приклад і що глузуючі рамки дозволяють отримати досить складні специфікації очікуваної поведінки від компонентів для підтримки комплексних тестів. Є багато матеріалу про макетні об’єкти та глузуючі рамки для отримання додаткової інформації.
Об'єкт макету - це об'єкт, який замінює реальний об'єкт. В об'єктно-орієнтованому програмуванні макетні об’єкти моделюються об'єктами, що імітують поведінку реальних об'єктів контрольованими способами.
Комп'ютерний програміст, як правило, створює макетний об'єкт для перевірки поведінки якогось іншого об'єкта, приблизно так само, як дизайнер автомобілів використовує манекен на тест на аварійний рух, щоб імітувати динамічну поведінку людини при ударах автомобіля.
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
макет запрограмовані на очікувані результати.
Об'єкти макету - це імітовані об’єкти, що імітують поведінку реальних. Зазвичай ви пишете макетний об'єкт, якщо:
Об'єкт 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());
Таким чином ми можемо легко протестувати розвиток драйвів класів, які залежать від інших класів.
Я настійно рекомендую чудову статтю Мартіна Фаулера, яка пояснює, що саме є макетами та чим вони відрізняються від заглушок.
Під час тестування певної частини комп'ютерної програми в ідеалі ви хочете перевірити саме поведінку саме цієї частини.
Наприклад, подивіться на псевдо-код нижче з уявного фрагмента програми, яка використовує іншу програму для виклику надрукувати щось:
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
Об'єкти макету та заглушки є важливою частиною тестування одиниць. Насправді вони проходять довгий шлях, щоб переконатися, що ви протестуєте одиниці , а не групи одиниць.
Коротше кажучи, ви використовуєте заглушки, щоб розбити залежність 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()
не вийде з ладу.
Як ще одна відповідь запропонована за посиланням на " Знущання не заглушки ", макети є формою "тестового подвійного", що використовується замість реального об'єкта. Те, що їх відрізняє від інших форм тестових пар, таких як об'єкти-заглушки, полягає в тому, що інші тестові парні пропонують перевірку стану (і, можливо, моделювання), тоді як знущаються пропонують перевірку поведінки (і необов'язково моделювання).
За допомогою заглушки ви можете зателефонувати декількома методами на заглушці в будь-якому порядку (або навіть повторно) і визначити успіх, якщо заглушка набула значення або стан, який ви намітили. Навпаки, макетний об’єкт очікує виклику дуже специфічних функцій у певному порядку та навіть певній кількості разів. Тест з макетним об’єктом буде вважатися "невдалим" просто тому, що методи викликалися в іншій послідовності або рахунку - навіть якщо об'єкт макету мав правильний стан при завершенні тесту!
Таким чином, макетні об'єкти часто вважаються більш щільно пов'язаними з кодом SUT, ніж об'єкти stub. Це може бути хорошою чи поганою річчю, залежно від того, що ви намагаєтеся перевірити.
Частина сенсу використання макетних об'єктів полягає в тому, що їх не потрібно реально реалізовувати відповідно до специфікації. Вони можуть просто дати відповіді на манекени. Наприклад, якщо вам доведеться реалізувати компоненти A і B і обидва "дзвонити" (взаємодіяти) один з одним, ви не можете перевірити A, поки B не буде реалізовано, і навпаки. У тестовій розробці це проблема. Таким чином, ви створюєте макетні ("манекени") об'єкти для A і B, які дуже прості, але вони дають певну відповідь при взаємодії з ними. Таким чином, ви можете реалізувати та протестувати A, використовуючи макетний об’єкт для B.
Для php та phpunit добре пояснено у документації phpunit. дивіться тут документацію phpunit
В простому слові глузуючий об'єкт - це просто манекенний об'єкт вашого вихідного і повертає його повернене значення, це повернене значення може бути використане в тестовому класі
Це одна з головних перспектив одиничних тестів. так, ви намагаєтеся перевірити свою єдину одиницю коду, і ваші результати тестування не повинні відповідати поведінці інших бобів або об'єктів. тож вам слід знущатися над ними, використовуючи Mock об'єкти з деякою спрощеною відповідною відповіддю.