Блок тестування поведінки без з'єднання з деталями реалізації


16

У своїй розмові TDD, куди це все пішло не так , Іен Купер підштовхує оригінальний намір Кента Бека за тестуванням одиниці в TDD (для тестування поведінки, а не методів занять конкретно) та заперечує за те, щоб уникнути прив'язки тестів до впровадження.

У випадку поведінки на зразок save X to some data sourceсистеми з типовим набором сервісів і сховищ, як ми можемо перевірити збереження деяких даних на рівні сервісу через сховище, не зв'язуючи тест з деталями реалізації (наприклад, викликати певний метод )? Хіба уникнути подібних зв’язків насправді не варто певних зусиль / поганого?


1
Якщо ви хочете перевірити, чи були дані збережені у сховищі, тоді тест повинен буде насправді перейти і перевірити сховище, щоб побачити, чи є дані, правда? Або я щось пропускаю?

Моє запитання було більше про те, щоб уникнути зв’язування тестів з деталями реалізації, як-от виклик певного методу у сховищі, чи справді, якщо це щось потрібно зробити.
Енді Хант

Відповіді:


8

Ваш конкретний приклад - це випадок, який вам зазвичай доводиться перевіряти, перевіряючи, чи був викликаний певний метод, оскільки це saving X to data sourceозначає спілкування із зовнішньою залежністю , тому поведінка, яку ви маєте перевірити, полягає в тому, що спілкування відбувається як очікувалося .

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

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

PS: Я не маю наміру говорити, чи тестування за допомогою State або Interactions - єдиний вірний спосіб TDD - я вважаю, що це питання використання правильного інструменту для правильної роботи.


Якщо ви згадуєте про "спілкування із зовнішньою залежністю", ви ставитеся до зовнішніх залежностей як до тих, які є зовнішніми для тестуваної одиниці, або до зовнішніх для системи в цілому?
Енді Хант

Під "зовнішньою залежністю" я маю на увазі все, що можна вважати плагіном до вашої програми. Під застосуванням я маю на увазі ділові правила, агностики будь-яких деталей, наприклад, яку рамку використовувати для стійкості чи інтерфейсу користувача. Я думаю, що дядько Боб може пояснити це краще, як у цій розмові: youtube.com/watch?v=WpkDN78P884
MichelHenrich

Я думаю, що це ідеальний підхід, як говориться в розмові, для тестування на основі "ознаки" чи "поведінки", і один тест на особливість чи поведінку (або перестановка одного, тобто різних параметрів). Однак, якщо у мене є 1 "щасливий" тест для функції, щоб зробити TDD, це означає, що у мене буде одна гігантська фіксація (і перегляд коду) для цієї функції, що є поганою ідеєю. Як би цього уникнути? Напишіть частину цієї функції як тест і весь код, пов'язаний з нею, а потім поступово додайте решту функції в наступних комітах?
jordan

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

7

Моя інтерпретація цієї розмови:

  • тестові компоненти, а не класи.
  • перевірити компоненти через їх порти інтерфейсу.

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

  • ви розробляєте систему для користувачів, а не, скажімо, бібліотеку корисних програм або рамки.
  • мета тестування - успішно поставити якомога більше в рамках конкурентного бюджету.
  • компоненти написані єдиною, зрілою, ймовірно, статично набраною мовою, як C # / Java.
  • компонент порядку 10000-50000 рядків; проект Maven або VS, плагін OSGI тощо.
  • компоненти написані одним розробником або тісно інтегрованою командою.
  • ви дотримуєтесь термінології та підходу чогось подібного до шестикутної архітектури
  • Порт компонента - це місце, де ви залишаєте локальну мову та її тип системи позаду, переходячи на http / SQL / XML / байти / ...
  • для обгортання кожного порту вводяться інтерфейси, у сенсі Java / C #, які можуть мати реалізації, вимкнені для комутації технологій.

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

Екстраполюючи від цього і вкладаючи в себе свої думки,

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

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

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

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

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

Зауважте, що навіть тут, якщо ви використовуєте безпечний для зловмисників інструмент типу mockito , перейменування методу інтерфейсу не може спричинити збій тесту. Якщо ви використовуєте IDE з завантаженими тестами, він буде відновлений разом із методом перейменування. Якщо ви цього не зробите, тест не складеться.


Чи можете ви описати / надати конкретний приклад інтерфейсного порту?
PositiveGuy

що є прикладом вихідного інтерфейсу. Чи можете ви бути конкретні в коді? Те саме з вхідним інтерфейсом.
PositiveGuy

Інтерфейс (у сенсі Java / C #) завершує порт, який може бути будь-чим, що спілкується із зовнішнім світом (d / b, socket, http, ....). Вихідний інтерфейс - це той, який не має методів із поверненими значеннями, які надходять із зовнішнього світу через порт, лише винятки або еквівалент.
soru

Інтерфейс введення - навпаки, співпрацювач - це і вхід, і вихід.
soru

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

0

Моя пропозиція - використовувати підхід до тестування, заснований на державі:

GIVEN У нас є тестовий БД у відомому стані

КОЛИ Служба викликається аргументами X

ТОГО Стверджуйте, що БД змінився від початкового стану до очікуваного стану, викликавши методи сховища лише для читання та перевіряючи їх повернені значення

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

Єдине з'єднання тут - виклик методу обслуговування та виклики сховища, необхідні для зчитування даних з БД, що добре.

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