Види одиничних тестів на основі корисності


13

З ціннісної точки зору я бачу у своїй практиці дві групи одиничних тестів:

  1. Тести, які перевіряють деяку нетривіальну логіку. Написання їх (перед реалізацією чи після) виявляє деякі проблеми / потенційні помилки та допомагає бути впевненими у випадку зміни логіки в майбутньому.
  2. Тести, які перевіряють якусь дуже тривіальну логіку. Ці тести більше схожі на код документа (як правило, з макетами), ніж на тестування. Робочий процес обслуговування цих тестів - це не "змінилась логіка, тест став червоним - дякую Богу, що я написав цей тест", але "якийсь тривіальний код змінився, тест став помилково негативним - я повинен підтримувати (переписувати) тест, не отримуючи ніякого прибутку" . Більшість часу ці тести не варто підтримувати (крім релігійних причин). Згідно з моїм досвідом у багатьох системах, ці тести складають приблизно 80% усіх тестів.

Я намагаюся з’ясувати, що думають інші хлопці на тему поділу одиничних тестів за значенням та як це відповідає моєму розділенню. Але те, що я в основному бачу, це або пропаганда повного дня TDD, або тести - безрезультатна, просто записую коду. Мене цікавить щось посередині. Ваші власні думки чи посилання на статті / статті / книги вітаються.


3
Я постійно перевіряю одиничні тести на наявність відомих (специфічних) помилок, які колись прослизали через оригінальний набір тестів, - як окрема група, роль якої полягає у запобіганні регресії помилок.
Конрад Моравський

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

3
@Telastyn - Все, що стосується твого коментаря, здається мені абсолютно божевільним. Хто навмисно ускладнить зміну коду? Навіщо перешкоджати розробникам змінювати код так, як вони вважають за потрібне - ви їм не довіряєте? Вони погані розробники?
Бенджамін Ходжсон

2
У будь-якому випадку, якщо зміна коду, як правило, має "ефект пульсації", то у вашого коду виникає проблема дизайну - у цьому випадку розробників слід заохочувати до рефактора, наскільки це розумно. Тендітні тести активно відштовхують рефакторинг (тест не вдається; кому можна потурбуватись, чи був цей тест одним із 80% тестів, які насправді нічого не роблять? Ви просто знайдете інший, більш складний спосіб зробити це). Але ти, здається, сприймаєш це як бажану характеристику ... Я цього зовсім не розумію.
Бенджамін Ходжсон

2
Так чи інакше, ОП може вважати цікавим цей допис у блозі від творця Rails. Щоб значно спростити його точку зору, вам, мабуть, слід спробувати викинути ці 80% тестів.
Бенджамін Ходжсон

Відповіді:


14

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

Перша проблема, яку я бачу з тестами, полягає в тому, що їх легко помилити. У моєму коледжі C ++ клас ми піддавались одиничним тестам як у першому, так і у другому семестрі. Ми взагалі нічого не знали про програмування в будь-якому семестрі - ми намагалися вивчити основи програмування за допомогою C ++. А тепер уявіть собі сказати студентам: "Ой, ви написали невеликий щорічний калькулятор податку! Тепер напишіть кілька одиничних тестів, щоб переконатися, що він працює правильно". Результати повинні бути очевидними - всі вони були жахливими, включаючи мої спроби.

Як тільки ти визнаєш, що ти смоктаєш на написання одиничних тестів і хочеш покращитися, незабаром ти зіткнешся або з модними стилями тестування, або з різними методологіями. Під методами тестування я маю на увазі таку практику, як тест-перше або те, що робить Ендрю Бінсток з DrDobbs, а це написання тестів поряд із кодом. У обох є свої плюси і мінуси, і я відмовляюся вникати в будь-які суб'єктивні деталі, тому що це підбурює полум'яну війну. Якщо вас не бентежить, яка методологія програмування краща, то, можливо, стиль тестування зробить трюк. Чи варто використовувати TDD, BDD, тестування на основі властивостей? JUnit має вдосконалені концепції під назвою Теорії, які розмивають межу між тестуванням TDD та тестуванням на основі властивостей. Який використовувати коли?

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

Для суб’єктивної думки я вважаю за краще писати «фази» тестів, через відсутність кращої фрази. Я пишу одиничні тести, які тестують класи ізольовано, використовуючи макети, коли це необхідно. Вони, ймовірно, будуть виконані за допомогою JUnit або чогось подібного. Тоді я пишу тести на інтеграцію або прийняття, вони виконуються окремо і, як правило, лише кілька разів на день. Це ваш нетривіальний випадок використання. Зазвичай я використовую BDD, оскільки приємно виражати функції природною мовою, що JUnit не може легко надати.

Нарешті, ресурси. Вони представлять суперечливі думки, здебільшого зосереджені навколо тестування одиниць на різних мовах та з різними рамками. Вони повинні представити розрив в ідеології та методології, дозволяючи вам скласти власну думку, доки я вже надто не маніпулював вашою :)

[1] Корупція Agile Ендрю Бінстока

[2] Відповідь на відповіді попередньої статті

[3] Відповідь на корупцію спритного дядька Боба

[4] Відповідь на корупцію Agile Роб Майерс

[5] Навіщо турбуватися з тестуванням огірків?

[6] Ти його неправильно випиваєш

[7] Крок від інструментів

[8] Коментар до "Римських цифр Ката з коментарем"

[9] Римські цифри Ката з коментарем


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

1
@Craig: Точно! Це я мав на увазі, не знаючи, як правильно написати тести. Як студент коледжу, таксист був одним великим класом, написаним без належного розуміння SOLID. Ви абсолютно правильно вважаєте, що це скоріше інтеграційний тест, ніж будь-що інше, але це був невідомий для нас термін. Нас професор піддавав лише "одиничним" тестам.
IAE

5

Я вважаю, що важливо мати тести обох типів та використовувати їх, де це доречно.

Як ви сказали, є дві крайності, і я, чесно, не згоден ні з однією, ні з однією.

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

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

Я твердо вірю в тестування одиниць і використовую TDD там, де це має сенс. Одиночні тести, безумовно, приносять значення у вигляді підвищеної якості та "невдалої поведінки" при зміні коду. Однак слід пам’ятати і про старе правило 80/20 . У якийсь момент ви досягнете зменшення прибутку під час написання тестів, і вам потрібно перейти до більш продуктивної роботи, навіть якщо є певне вимірюване значення, яке ви мали б написати більше тестів.


Складання тесту, щоб переконатися, що система відстежує вік людини, не є одиничним тестом, IMO. Це інтеграційний тест. Тест одиниці перевіряє загальну одиницю виконання (також "процедуру"), яка, скажімо, обчислює вікове значення, скажімо, від базової дати та зміщення в будь-які одиниці (дні, тижні тощо). Моя думка, що біт коду не повинен мати жодних дивних вихідних залежностей від решти системи. Він ДУЖЕ обчислює вік з пари вхідних значень, і в цьому випадку тест одиниці може підтвердити правильну поведінку, яка, ймовірно, є винятком, якщо зміщення призводить до негативного віку.
Крейг

Я не мав на увазі жодного розрахунку. Якщо модель зберігає фрагмент даних, вона може перевірити, що дані належать до правильного домену. У цьому випадку домен - це сукупність негативних цілих чисел. Розрахунки повинні відбуватися в контролері (в MVC), і в цьому прикладі розрахунок віку буде окремим тестом.

4

Ось я взяв на себе це рішення: всі тести мають витрати:

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

Ми також маємо намір для всіх тестів надати переваги (і, на мій досвід, майже всі тести дають переваги):

  • специфікація
  • виділити кутові корпуси
  • запобігти регресу
  • автоматична перевірка
  • приклади використання API
  • кількісна оцінка специфічних властивостей (часу, простору)

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

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

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

У деяких випадках викачування коду без тестів може забезпечити найкращі вигоди / витрати. В інших випадках ретельний тестовий набір може бути кращим. У інших випадках покращення дизайну може бути найкращим ділом.


2

Що насправді є одиничним тестом? І чи справді тут грає така велика дихотомія?

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

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

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

Я скажу ні . Це інтеграційний тест. І ті, хто, безумовно, мають своє місце, але вони теж інша тема.

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

Отже, одиничний тест повинен мати дуже обмежений обсяг.

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

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

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

У багатьох випадках ви, ймовірно, повинні мати кілька тестів на одиницю. Самі тести повинні бути простими, тестуючи одну і лише одну поведінку, наскільки це можливо.

Поняття "одиничного тестування" тестування нетривіальної, складної, складної логіки, я думаю, трохи оксиморон.

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

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

Повертаючись до мого надуманим наприклад системи бронювання, ви дійсно не можете посилати запити від до живої базі даних резервування або третьої сторони обслуговування (або навіть «Dev» примірнику цього) кожен раз , коли ви блок тестового коду.

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

Але логіка самих одиничних тестів залишається максимально простою.


1

Це, звичайно, лише моя думка, але, витративши останні кілька місяців на вивчення функціонального програмування на fsharp (що йде з C # фону), змусив мене зрозуміти кілька речей.

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

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

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

З того моменту, як я почав кодувати Fsharp, мені не потрібно було нічого глузуючої рамки, і я навіть прогнав свою залежність взагалі від контейнера IOC. Мої тести визначаються потребою та вартістю бізнесу, а не на важких шарах абстрагування, як правило, необхідних для досягнення складу в імперативному програмуванні.

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