Як написати «добрі» одиничні тести?


61

Запущений цією ниткою , я (знову) замислююся про те, щоб нарешті використати одиничні тести у своїх проектах. Кілька плакатів там говорять щось на кшталт "Тести класні, якщо вони хороші тести". Моє запитання зараз: Що таке "хороші" тести?

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


8
Будь-яка хороша одинична перевірка повинна перевірити лише одне - якщо вона не вдасться, ви повинні точно знати, що пішло не так.
габлін

2
Якщо у вас є велика кількість даних, хороша справа - написати загальні тести, які можуть приймати файли даних як вхідні дані. Файли даних зазвичай повинні містити як вхідний, так і очікуваний результат. За допомогою тестових рамок xunit ви можете генерувати тестові випадки на льоту - по одному для кожного зразка даних.
froderik

2
@gablin "Якщо це не вдасться, ви повинні точно знати, що пішло не так", це підказало б, що тести з декількома можливими причинами відмов у порядку, поки ви можете визначити причину з результатів тесту ...?
користувач253751

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

Відповіді:


52

Про тестування одиничного мистецтва можна сказати наступне:

Тест одиниці повинен мати такі властивості:

  • Він повинен бути автоматизованим і повторюваним.
  • Це має бути легко здійснити.
  • Після написання він повинен залишитися для подальшого використання.
  • Будь-хто повинен мати можливість це запустити.
  • Він повинен працювати лише одним натисканням кнопки.
  • Він повинен швидко працювати.

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

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

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


1
+1 для вичерпного списку, орієнтованого на тестування одиниць (не на інтеграцію чи функціональне тестування)
Gary Rowe

1
+1 для посилання. Цікавий матеріал там можна знайти.
Joris Meys

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

1
коли це говорить It should run at the push of a button, чи означає це, що для тестування блоку не потрібно вимагати ні контейнерів (сервер додатків), що працюють (для блоку, який тестується), ні з'єднання з ресурсами (наприклад, БД, зовнішні веб-служби тощо)? Мене просто заплутало те, які частини програми повинні бути перевірені, а які - не. Мені сказали, що одиничні тести не повинні залежати від з'єднання БД та запущених контейнерів, а можливо, замість цього використовуються макети.
амфібій

42

Хороший тест одиниці не відображає функцію, яку він тестує.

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

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


21
+1 Що б ви зробили в цьому випадку, це тестування твердими аргументами та перевірка вашої відомої відповіді.
Майкл К

Я вже бачив цей запах.
Пол Різник

Чи можете ви навести приклад хорошого одиничного тесту для функції, яка повертає середні показники?
VLAS

2
Заздалегідь визначені значення @VLAS, наприклад переконайтеся, що середнє значення (1, 3) == 2, а також важливіше перевірити крайові випадки, такі як INT_MAX, нулі, від’ємні значення тощо. Якщо помилку було знайдено та виправлено у функції, додайте інший тест, щоб переконатися, що ця помилка ніколи не вводиться повторно.
mojuba

Цікаво. Як ви пропонуєте отримати правильні відповіді на ці тестові входи та потенційно не зробити ту саму помилку, що і код, який піддавався тесту?
Тимо

10

Хороші одиничні випробування - це по суті специфікація у виконанні:

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

Я виявив, що Test-Driven-Development дуже добре підходить для процедур бібліотеки, оскільки ви, по суті, спочатку пишете API, а потім реальна реалізація.


7

для TDD "хороші" тестові функції тесту, які бажає замовник ; функції не обов'язково відповідають функціям, а тестові сценарії розробник не повинен створювати у вакуумі

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

Приклад історії:

Як пілот X-Wing, я хочу [не більше 0,0001% помилки пристосування], щоб [цільовий комп'ютер міг потрапити у вихлопний порт Death Star при переміщенні на повній швидкості через коробковий каньйон]

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

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

//Scenario 1 - can you hit the side of a barn?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is zero
    and all other values are constant,
Then:
    the error coefficient must be zero

//Scenario 2 - can you hit a turtle?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is less than c
    and all other values are constant,
Then:
    the error coefficient must be less than 0.0000000001/ns

...

//Scenario 42 - death blossom
Given:
    all 7 channels with 30% dropout and a 0.05 second sampling window
When:
    speed is zero
    and position is within enemy cluster
    and all targets are stationary
Then:
    the error coefficient must be less than 0.000001/ns for each target

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


5

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


5

Я бачив безліч випадків, коли люди вкладають величезну кількість зусиль, пишучи тести для коду, який рідко вводиться, а не пишуть тести на код, який вводять часто.

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

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


але що пізніше, коли бібліотека має новішу версію з виправленням помилок?

@ Thorbjørn Ravn Andersen - Це залежить від бібліотеки, що змінилося та власного процесу тестування. Я не збираюся писати тести на код, який, як я знаю, працює, коли його скинув на місце, і ніколи не торкаюся. Отже, якщо це працює після оновлення, це не виходить :) Звичайно, є винятки.
Tim Post

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

... і якщо це змінилося, тестуйте речі, які споживають згадану бібліотеку ... tl; dr; Мені не потрібно тестувати внутрішні сторони коду. Однак відповідь оновлена ​​для наочності.
Тім Пост

4

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


3

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

Зазвичай у мене є один або кілька тестів на успіх, а потім, можливо, кілька тестів на невдачу за публічним методом.

Я багато використовую макети. Гарний фреймворк, ймовірно, міг би бути дуже корисним, наприклад, PowerMock. Хоча я ще не використовую жодного.

Якщо клас A використовує інший клас B, я б додав інтерфейс X, щоб A не використовував B безпосередньо. Тоді я б створив макет XMockup і використовувати його замість B у своїх тестах. Це дійсно допомагає прискорити виконання тесту, зменшує складність тесту, а також зменшує кількість тестів, які я пишу для A, оскільки мені не доведеться справлятися з особливостями B. Я можу, наприклад, тестувати, що A викликає X.someMethod () замість побічного ефекту викликати B.someMethod ().

Утримуйте і тестовий код чистим.

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


2

Я бачу, що Ендрі Лоурі вже розмістив тестові показники одиниці Роя Ошерова; але, схоже, ніхто не представив (безкоштовний) набір, який дає дядько Боб у « Чистому коді» (132-133). Він використовує абревіатуру ПЕРШИЙ (тут зі своїми резюме):

  • Швидкий (вони повинні бігати швидко, так що люди не проти керувати ними)
  • Незалежні (тести не повинні робити налаштування чи відмовлятися одне для іншого)
  • Повторний (має працювати у всіх середовищах / платформах)
  • Самовивірка (повністю автоматизована; вихід повинен бути або "пройти", або "провалити", а не файл журналу)
  • Своєчасно (коли їх писати - безпосередньо перед написанням виробничого коду, який вони перевіряють)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.