Визначення, що є корисним одиничним тестом


47

Я переглядав документи phpunit і зіткнувся з наступною цитатою:

Завжди можна написати більше тестів. Однак ви швидко виявите, що лише частина тестів, які ви можете уявити, насправді корисні. Те, що ви хочете, - це написати тести, які виходять з ладу, навіть якщо ви думаєте, що вони повинні працювати, або тести, які досягли успіху, хоча ви вважаєте, що вони повинні вийти з ладу. Ще один спосіб подумати про це - з точки зору витрат / вигод. Ви хочете написати тести, які повернуть вам інформацію. --Erich Gamma

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

Тож якщо справа не в тестуванні, про що це? Йдеться про з'ясування того, що ти намагаєшся зробити, перш ніж бігти напівроком, щоб спробувати це зробити. Ви пишете специфікацію, яка припускає невеликий аспект поведінки у стислій, однозначній та виконуваній формі. Це так просто. Це означає, що ви пишете тести? Ні. Це означає, що ви пишете технічні характеристики того, що повинен робити ваш код. Це означає, що ви вчасно визначаєте поведінку свого коду. Але далеко не достроково. Насправді, перед тим, як написати код, найкраще, тому що тоді у вас є стільки інформації, скільки під рукою. Як і добре зроблена TDD, ви працюєте невеликими кроками ... уточнюючи один невеликий аспект поведінки за один раз, потім реалізуючи його. Коли ви зрозумієте, що справа в тому, щоб вказати на поведінку, а не писати тести, ваша точка зору змінюється. Раптом думка про тестовий клас для кожного з ваших виробничих класів смішно обмежує. І думка про тестування кожного з ваших методів власним методом тестування (у взаємозв'язку 1-1) буде смішною. --Dave Astels

Важливим розділом цього є

* І думка про тестування кожного з ваших методів за допомогою власного методу тестування (у співвідношенні 1-1) буде смішною. *

Отже, якщо створення тесту для кожного методу є "смішним", як / коли ви обирали те, для чого пишете тести?


5
Це гарне запитання, але я вважаю, що це питання незалежно від мови програмування - чому ви позначили його php?

Ось кілька справді хороших статей, які дали мені чітке керівництво щодо того, які одиничні тести я повинен писати та які напрямки важливі для автоматизованих тестів. - blog.stevensanderson.com/2009/11/04/… - blog.stevensanderson.com/2009/08/24/… - ayende.com/blog/4218/scenario-driven-tests
Кейн

Відповіді:


27

Скільки тестів на метод?

Ну і теоретичний і дуже непрактичний максимум - це складність N-Path (припустимо, тести охоплюють різні способи через код;)). Мінімум ОДИН !. За загальнодоступним методом, тобто він не перевіряє деталі реалізації, лише зовнішню поведінку класу (повертає значення та викликає інші об'єкти).

Ви цитуєте:

* І думка про тестування кожного з ваших методів за допомогою власного методу тестування (у співвідношенні 1-1) буде смішною. *

а потім запитайте:

Отже, якщо створення тесту для кожного методу є "смішним", як / коли ви обирали те, для чого пишете тести?

Але я думаю, ви тут неправильно зрозуміли автора:

Ідея мати one test methodпер one method in the class to test- це те, що автор називає "сміхотворним".

(Принаймні для мене) Мова не про те, що "менше", а про "більше"

Тож дозвольте перефразувати так, як я його зрозумів:

І думка про тестування кожного з ваших методів ТІЛЬКИ ОДНІМ МЕТОДОМ (власний метод тестування у взаємозв'язку 1-1) буде смішною.

Щоб процитувати ще раз:

Коли ви зрозумієте, що справа в тому, щоб вказати на поведінку, а не писати тести, ваша точка зору зміниться.


Коли ви практикуєте TDD, ви не думаєте :

У мене є метод, calculateX($a, $b);і він потребує тесту, testCalculcateXякий перевіряє ВСЕ, що стосується методу.

Що TDD говорить вам, це подумати про те, що ваш код повинен робити :

Мені потрібно обчислити більше двох значень ( перший тестовий випадок! ), Але якщо $ a менше нуля, то це повинно призвести до помилки ( другий тестовий випадок! ), А якщо $ b менше нуля, він повинен .... ( третій тестовий випадок! ) тощо.


Ви хочете перевірити поведінку, а не лише окремі методи без контексту.

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


Як ви вирішите вирішити, для якого фрагмента коду ви створите одиничні тести?

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

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

TDD - це спосіб переконатися, що у вас є тести на ВСЕ, але поки ви будете писати тести, це добре. Зазвичай їх написання в один і той же день допомагає, оскільки ви не збираєтеся це робити пізніше, чи не так? :)



Відповідь на коментарі:

пристойну кількість методів не можна перевірити в конкретному контексті, оскільки вони або залежать, або залежать від інших методів

Ну є три речі, якими можна назвати ці методи:

Публічні методи інших класів

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

* Захищені або приватні методи на тому самому *

Все, що не входить у загальнодоступний API класу, зазвичай не тестується безпосередньо.

Ви хочете перевірити поведінку, а не реалізацію, і якщо клас робить все, це працює в одному великому публічному методі або в багатьох менших захищених методах, які викликаються, це реалізація . Ви хочете змінити захищені методи БЕЗ торкання ваших тестів. Тому що ваші тести зламаються, якщо зміни вашого коду змінить поведінку! Ось для чого там ваші тести, щоб сказати вам, коли щось зламаєте :)

Публічні методи на одному класі

Це трапляється не дуже часто? І якщо це так, як у наступному прикладі, є кілька способів вирішити це:

$stuff = new Stuff();
$stuff->setBla(12);
$stuff->setFoo(14);
$stuff->execute(); 

Те, що встановники існують і не є частиною підпису методу Execute - це інша тема;)

Те, що ми можемо перевірити тут, - це те, чи виконується виконувати, коли ми встановлюємо неправильні значення. Це setBlaвиняток, коли ви передаєте рядок, може бути перевірено окремо, але якщо ми хочемо перевірити, що ці два дозволені значення (12 і 14) не працюють РАЗОМ (з будь-якої причини), ніж один тестовий випадок.

Якщо ви хочете "хорошого" тестового набору, ви можете, в php, можливо (!) Додати @covers Stuff::executeпримітку, щоб переконатися, що ви генеруєте лише покриття коду для цього методу, а інші речі, які є лише налаштуваннями, потрібно перевірити окремо (ще раз, якщо ти цього хочеш).

Тож справа в тому: Можливо, вам потрібно спершу створити дещо з навколишнього світу, але вам слід вміти писати змістовні тестові випадки, які зазвичай охоплюють лише одну чи, можливо, дві реальні функції (сетери тут не враховуються). Решта може бути вилучена з ефіру або спершу випробувана, а потім покладатися на них (див. @depends)


* Примітка: запитання було перенесено з SO і спочатку стосувалося PHP / PHPUnit, тому зразок коду та посилання є зі світу php, я думаю, це також застосовно до інших мов, оскільки phpunit не сильно відрізняється від інших xUnit тестування рамок.


Дуже детально та інформативно ... Ви сказали: "Ви хочете перевірити поведінку, а не лише окремі методи без контексту". Напевно пристойну кількість методів не можна перевірити в конкретному контексті, оскільки вони або залежать, або залежать від інших методів , тож корисна умова тестування була б контекстуальною лише в тому випадку, якщо також проходили тестування утриманців? Або я неправильно
трактую,

@ robinsonc494 Я відредагую в прикладі, який, можливо, трохи краще пояснює, куди я йду з цим
Edorian

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

@ robinsonc494 Можливо, подумайте про це так: Якщо ви вдарите когось, той ефір ударить назад, подзвоніть поліцейським або втечі. Така поведінка. Це те, що робить Людина. Той факт, що він використовує свої мідії, викликані невеликими електричними зарядами з його мозку, є реалізацією. Якщо ви хочете перевірити чиюсь реакцію, ви пробиваєте його і бачите, чи діє він так, як ви цього очікуєте. Ви не ставите його в сканер мозку і бачите, чи посилаються імпульси на мідії. Деякі ходять на заняття досить багато;)
edorian

3
Я думаю, що найкращим прикладом, який я побачив у TDD, який справді допоміг йому натиснути, як зробити тестові випадки, була гра «Боулінг Ката» дядька Боб Мартіна. slideshare.net/lalitkale/bowling-game-kata-by-robert-c-martin
Емі Анушевський

2

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

По-перше, якщо ми дотримуємося TDD або навіть DTH (тобто розробляємо і тестуємо в тісній гармонії), ми використовуємо тести, які ми пишемо, як фокус на те, щоб правильно надати дизайн. Розмірковуючи про кутові випадки і відповідно пишучи тести, ми уникаємо помилок в першу чергу, тому насправді ми пишемо тест, який ми очікуємо пройти (ОК прямо на початку TDD вони не вдається, але це лише артефакт замовлення, коли код зроблений, ми очікуємо, що вони пройдуть, і більшість це робить, тому що ви думали про код.

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

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

Щоб відповісти на ваше чітке запитання: Тест блоку для загальнодоступного інтерфейсу має значення.

відредаговано у коментарі у відповідь:

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


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

За допомогою покриття коду ви можете визначити, коли код у ваших приватних методах не виконується під час тестування ваших публічних методів. Якщо ваші загальнодоступні методи повністю охоплені, будь-які розкриті приватні методи, очевидно, не використовуються і їх можна видалити. Якщо ні, то вам потрібно більше тестів для ваших публічних методів.
Девід Харкнес

2

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

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

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

  • Зачекайте, поки API, на який ви тестуєте, стабільний, щоб написати будь-який тип тесту. Ви хочете уникати рефакторингу як вашої реалізації, так і тестів.

Роб Ештон має чудову статтю на цю тему, яку я сильно почерпнув, щоб сформулювати вищезазначені принципи.


+1 - Я не обов'язково погоджуюся з усіма вашими пунктами (або пунктами статті), але там, де я згоден, є те, що більшість одиничних тестів є марними, якщо робити їх наосліп (підхід TDD). Однак вони надзвичайно цінні, якщо ви розумні у вирішенні питання, на що гідно витрачати час на тестування одиниць. Я цілком погоджуюся з тим, що ви отримуєте набагато більший удар за долар при написанні випробувань вищого рівня, зокрема автоматизованого тестування на підсистемі. Проблема автоматичних тестів в кінці полягає в тому, що вони будуть важкими, якщо не зовсім непрактичними, для системи, якщо вона має будь-який розмір / складність.
Данк

0

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

Якщо ви пишете загальнодоступний API, це надзвичайно цінно. Однак вам завжди потрібна хороша доза інтегрованих тестів інтеграції, тому що наближатися до 100% тестового покриття, як правило, не варто, і пропустіть речі, які більшість вважатиме «нестабільними» методами тестування одиницями (глузуючі, тощо)

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