Чи макети порушують принцип "Відкрито / закрито"?


13

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

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

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

Мої запитання:

  1. Чи макети порушують принцип відкритого / закритого типу?
  2. Щось не вистачає в аргументі на користь заглушок в останньому абзаці, які роблять заглушки не такими великими проти макетів?
  3. Якщо так, коли було б корисним випадком використання глузування та коли було б корисним випадком використання заглушок?


8
Since mocking relays heavily on expectation calls from system under test's dependencies...Я думаю, що саме тут ти йдеш не так. Макет - це якесь штучне зображення зовнішньої системи. Він ні в якому разі не представляє зовнішню систему, за винятком випадків, коли вона імітує зовнішню систему таким чином, що дозволяє проводити тести проти коду, що має залежність від зазначеної зовнішньої системи. Вам все одно знадобляться інтеграційні тести, щоб довести, що ваш код працює з реальною, незамкненою системою.
Роберт Харві

8
Інакше кажучи, макет є реалізацією замінника. Саме тому ми запрограмовані в першу чергу на інтерфейс, щоб ми могли використовувати макети як резервні елементи для реальної реалізації. Іншими словами, макети розлучаються з реальною реалізацією , а не з’єднуються з нею.
Роберт Харві

3
"Іншими словами, тест повинен бути невдалим, якщо метод не працює, але не тому, що реалізація змінилася", це не завжди так. Існує маса обставин, коли ви повинні змінити як вашу реалізацію, так і свої тести.
whatsisname

Відповіді:


4
  1. Я не бачу, чому глузування порушують принцип відкритого / закритого типу. Якщо ви можете пояснити нам, чому ви думаєте, що вони можуть, то ми можемо полегшити вашу проблему.

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

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

PS Особа, про яку ви посилаєтесь, міг би бути я, в одному з моїх інших відповідей, пов’язаних з тестуванням, тут, на програмі programmers.stackexchange.com, наприклад у цьому .


an alternative implementation would be a full-blown in-memory RDBMS- Вам не обов’язково так далеко заїжджати з заглушкою.
Роберт Харві

@RobertHarvey добре, з HSQLDB і H2 насправді не так складно йти так далеко. Напевно, складніше зробити щось напівзвукове, щоб не зайти так далеко. Але якщо ви вирішите це зробити самостійно, вам доведеться почати з написання аналізатора SQL. Звичайно, ви можете вирізати деякі куточки, але роботи дуже багато . У будь-якому випадку, як я вже говорив вище, це лише крайній приклад.
Майк Накіс

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

  2. Проблема тестових пар (mock / stub) полягає в тому, що ви в основному робите довільні припущення щодо взаємодії тестуваного класу зі своїм оточенням. Якщо ці очікування помиляються, то, швидше за все, ви матимете певні проблеми, коли код буде розгорнуто. Якщо ви можете собі це дозволити, протестуйте свій код у тих же обмеженнях, що й обмеження вашого виробничого середовища. Якщо ви не можете, зробіть щонайменше можливих припущень і знущайтеся / заглушуйте лише периферійні пристрої вашої системи (база даних, служба аутентифікації, клієнт HTTP тощо).

Єдина вагома причина, чому слід використовувати IMHO - подвійний, - це коли вам потрібно записати його взаємодію з тестуваним класом або коли вам потрібно надати підроблені дані (що можуть робити обидві методи). Однак будьте обережні, зловживання ним відображає поганий дизайн або тест, який занадто багато покладається на API, що перебуває під час тестування.


6

Примітка. Я припускаю, що ви визначаєте Mock як «клас без реалізації, просто щось, що ви можете відстежувати», а Stub - «частковий макет, він же використовує деяку реальну поведінку реалізованого класу», відповідно до цього стеку Питання про переповнення .

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

Як завжди, ви збираєтеся прочитати часткове попереджувальне попередження: Об'єктно-орієнтоване програмування - це менше вирішення складності, розділивши складність на окремі, специфічні об'єкти SRPy. Як часткова насмішка вписується в цю парадигму? Ну, це просто не так ... Частковий макет зазвичай означає, що складність була перенесена на інший метод одного і того ж об’єкта. У більшості випадків це не так, як ви хочете розробити свою програму.

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

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


2
З того ж наданого питання SO (прийнята відповідь), це визначення Mock / Stub, яке я також переглядав: Mock-об'єкти використовуються для визначення очікувань, тобто: У цьому сценарії я очікую, що метод A () буде викликаний з такими та такими параметрами. Знущаються записують та перевіряють такі очікування. Стюби, з іншого боку, мають інше призначення: вони не фіксують і не перевіряють очікування, а дозволяють нам "замінити" поведінку, стан "фальшивого" об'єкта, щоб використовувати тестовий сценарій ...
Крістофер Франсіско

2

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

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

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

Tl / Dr: робити це "за книжкою" - це приємно, але коли наштовхнутися на поштовх, мати продукт на столі свого шефа до п’ятниці є кориснішим, ніж набір тестових книг, який триває до теплової смерті Всесвіт для підтвердження відповідності.

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