У мене є загальне уявлення про помилкові і підроблених предметів, але я не впевнений , що у мене є передчуття , коли / де використовувати глузливий - особливо це буде ставитися до цього сценарієм тут .
У мене є загальне уявлення про помилкові і підроблених предметів, але я не впевнений , що у мене є передчуття , коли / де використовувати глузливий - особливо це буде ставитися до цього сценарієм тут .
Відповіді:
Тест одиниці повинен перевірити один кодовий шлях за допомогою єдиного методу. Коли виконання методу переходить за межі цього методу, в інший об’єкт і знову повертається, у вас виникає залежність.
Коли ви протестуєте цей шлях коду з фактичною залежністю, ви не є одиничним тестуванням; ви інтегруєте тестування. Хоча це добре і необхідно, це не одиничне тестування.
Якщо ваша залежність баггі, ваш тест може вплинути таким чином, щоб повернути хибний позитив. Наприклад, ви можете передати залежність несподіваною нуль, і залежність може не переходити на null, як це задокументовано. Ваш тест не містить нульового виключення аргументу, як він повинен бути, і тест проходить.
Крім того, вам може бути важко, якщо не неможливо, надійно змусити залежний об'єкт повернути саме те, що ви хочете під час тесту. Це також включає викидання очікуваних винятків у тестах.
Макет замінює цю залежність. Ви встановлюєте очікування від дзвінків до залежного об'єкта, встановлюєте точні значення повернення, які він повинен дати вам виконати потрібний тест, та / або які винятки викинути, щоб ви могли перевірити код обробки винятків. Таким чином ви можете легко протестувати відповідний пристрій.
TL; DR: знущайтеся над кожною залежністю, яку торкається ваш тест.
Обметні об'єкти корисні, коли ви хочете перевірити взаємодію між тестовим класом та певним інтерфейсом.
Наприклад, ми хочемо перевірити, що цей метод sendInvitations(MailServer mailServer)
викликає MailServer.createMessage()
рівно один раз, а також дзвонить MailServer.sendMessage(m)
рівно один раз, а інші методи не викликаються в MailServer
інтерфейсі. Це коли ми можемо використовувати макетні об’єкти.
За допомогою макетних об’єктів замість того, щоб пройти реальний MailServerImpl
або тест TestMailServer
, ми можемо пройти макетну реалізацію MailServer
інтерфейсу. Перш ніж ми передамо макет MailServer
, ми "тренуємо" його, щоб він знав, який метод вимагає очікувати та які повертаючі значення повертати. Зрештою, макетний об'єкт стверджує, що всі очікувані методи називались як очікувалося.
Теоретично це звучить добре, але є і деякі недоліки.
Якщо у вас є макетна рамка, ви спокушаєтесь використовувати об'єкт макету кожен раз, коли вам потрібно передати інтерфейс класу під тестом. Таким чином ви закінчуєте тестування взаємодій, навіть коли це не потрібно . На жаль, небажане (випадкове) тестування взаємодій погано, тому що ви тестуєте, що певна вимога реалізується певним чином, замість того, щоб реалізація дала необхідний результат.
Ось приклад у псевдокоді. Припустимо, ми створили MySorter
клас і хочемо перевірити його:
// the correct way of testing
testSort() {
testList = [1, 7, 3, 8, 2]
MySorter.sort(testList)
assert testList equals [1, 2, 3, 7, 8]
}
// incorrect, testing implementation
testSort() {
testList = [1, 7, 3, 8, 2]
MySorter.sort(testList)
assert that compare(1, 2) was called once
assert that compare(1, 3) was not called
assert that compare(2, 3) was called once
....
}
(У цьому прикладі ми припускаємо, що це не те саме алгоритм сортування, як швидкий сортування, який ми хочемо перевірити; в цьому випадку останній тест був би дійсно дійсним.)
У такому крайньому прикладі очевидно, чому останній приклад помиляється. Коли ми змінюємо реалізацію MySorter
, перший тест виконує велику роботу, щоб переконатися, що ми все-таки правильно сортуємо, у чому полягає вся суть тестів - вони дозволяють нам безпечно змінити код. З іншого боку, останній тест завжди ламається і він активно шкідливий; це перешкоджає рефакторингу.
Макетні рамки часто дозволяють також менш суворо використовувати, де нам не потрібно точно вказувати, скільки разів слід викликати методи та які параметри очікуються; вони дозволяють створювати макетні об’єкти, які використовуються як заглушки .
Припустимо, у нас є метод, sendInvitations(PdfFormatter pdfFormatter, MailServer mailServer)
який ми хочемо перевірити. PdfFormatter
Об'єкт може бути використаний для створення запрошення. Ось тест:
testInvitations() {
// train as stub
pdfFormatter = create mock of PdfFormatter
let pdfFormatter.getCanvasWidth() returns 100
let pdfFormatter.getCanvasHeight() returns 300
let pdfFormatter.addText(x, y, text) returns true
let pdfFormatter.drawLine(line) does nothing
// train as mock
mailServer = create mock of MailServer
expect mailServer.sendMail() called exactly once
// do the test
sendInvitations(pdfFormatter, mailServer)
assert that all pdfFormatter expectations are met
assert that all mailServer expectations are met
}
У цьому прикладі нас не дуже цікавить PdfFormatter
об'єкт, тому ми просто навчимо його спокійно приймати будь-який виклик та повертати певні розумні значення повернення для всіх методів, що sendInvitation()
трапляються в цей момент. Як ми придумали саме цей перелік методів навчання? Ми просто провели тест і продовжували додавати методи, поки тест не пройшов. Зауважте, що ми навчили заглушку реагувати на метод, не маючи поняття, чому його потрібно викликати, ми просто додали все, на що скаржився тест. Ми раді, тест проходить.
Але що станеться пізніше, коли ми змінимо sendInvitations()
або якийсь інший клас, який sendInvitations()
використовує, щоб створити більш фантазійні pdfs? Наш тест несподівано провалюється, тому що тепер PdfFormatter
викликається більше методів , і ми не навчили заглушку їх очікувати. І, як правило, це не один тест, який дає збій у подібних ситуаціях, це будь-який тест, який трапляється безпосередньо чи опосередковано використовувати sendInvitations()
метод. Ми повинні виправити всі ці тести, додавши більше тренувань. Також зауважте, що ми не можемо видалити методи, які більше не потрібні, оскільки ми не знаємо, який із них не потрібен. Знову ж таки це заважає рефакторингу.
Крім того, читабельність тесту страшенно постраждала, є багато коду, який ми не писали через те, що хотіли, а тому, що нам довелося; ми не хочемо цього коду там. Тести, які використовують макети, виглядають дуже складно і часто важко читати. Тести повинні допомогти читачеві зрозуміти, як слід використовувати клас під тестом, таким чином вони повинні бути простими та зрозумілими. Якщо вони не читаються, їх ніхто не збирається підтримувати; насправді їх простіше видалити, ніж зберегти.
Як це виправити? Легко:
PdfFormatterImpl
. Якщо це неможливо, змініть реальні класи, щоб це стало можливим. Неможливість використання класу в тестах зазвичай вказує на деякі проблеми з класом. Виправлення проблем - виграшна ситуація - ви виправили клас і у вас простіший тест. З іншого боку, не виправити це та використовувати макети - це безрезультатна ситуація - ви не виправили реальний клас і у вас складніші, менш читабельні тести, які перешкоджають подальшій рефакторингу.TestPdfFormatter
що нічого не робить. Таким чином ви можете змінити його один раз для всіх тестів, і ваші тести не захаращені довгими налаштуваннями, де ви тренуєте заглушки.Загалом, макетні об’єкти мають своє використання, але коли їх не застосовують обережно, вони часто заохочують погану практику, перевіряючи деталі впровадження, перешкоджають рефакторингу та створюють важкі для читання та важкі для обслуговування тести .
Детальніше про недоліки знущань див. Також Об'єкти глузування: Недоліки та випадки використання .
Практичне правило:
Якщо функції, яку ви тестуєте, потрібен складний об'єкт як параметр, і було б просто інстанціювати цей об'єкт (якщо, наприклад, він намагається встановити TCP-з'єднання), використовуйте макет.
Ви повинні знущатися над об'єктом, коли у вас є залежність від одиниці коду, яку ви намагаєтеся перевірити, що це повинно бути "просто так".
Наприклад, коли ви намагаєтеся перевірити якусь логіку у своїй одиниці коду, але вам потрібно отримати щось від іншого об'єкта, і те, що повернуто з цієї залежності, може вплинути на те, що ви намагаєтеся перевірити - знущайтеся над цим об’єктом.
Чудовий подкаст на цю тему можна знайти тут