Наскільки глибокі ваші модульні тести?


88

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

Моє питання полягає в тому, на якому рівні деталізації ви пишете свої модульні тести?

..і є випадок тестування занадто багато?

Відповіді:


221

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

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


40
Світ не думає, що Кент Бек сказав би це! Є легіони розробників, які сумлінно переслідують 100% охоплення, тому що вони думають, що це зробив би Кент Бек! Я сказав багатьом, що ви сказали у своїй книзі про XP, що ви не завжди дотримуєтесь Тестування спочатку релігійно. Але я теж здивований.
Charlie Flowers,

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

2
Мене не цікавить висвітлення. Мені дуже цікаво, як часто пан Бек робить код, який не був написаний у відповідь на невдалий тест.
sheldonh

1
@RicardoRodrigues, ти не можеш писати тести, щоб охопити код, який інші люди напишуть пізніше. Це їх відповідальність.
Kief

2
Це не те, що я писав, уважно читайте; Я писав, що якщо ви пишете тести, щоб охопити лише частину, ВАШ власний код, залишивши непокриті частини, де "ви знали, що не допускаєте помилок", і ці частини змінюються і не мають належних тестів, у вас тут проблема, і це зовсім не TDD.
Рікардо Родрігес,

20

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

  1. Виправлена ​​помилка;
  2. Помилка не з’явиться знову.

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


Зі кількістю помилок, які я пишу, це може стати протиріччям. З 100 тестами коду, де щось не вдалося, це може означати, що ваші тести стають нечитабельними, і коли настає час переписати ці тести, це може стати накладними витратами.
Джонно Нолан

@JohnNolan: Чи так важлива читабельність тесту? ІМХО це не так, принаймні для цих специфічних тестів на регресію. Якщо ви часто переписуєте тести, можливо, ви тестуєте на занадто низькому рівні - в ідеалі ваші інтерфейси повинні залишатися відносно стабільними, навіть якщо ваші реалізації змінюються, і ви повинні тестувати на рівні інтерфейсу (хоча я розумію, що реальний світ часто не мені це подобається ...: - /) Якщо ваші інтерфейси сильно змінюються, я віддав би перевагу більшості або всім цим специфічним тестам на помилку, аніж переписував їх.
j_random_hacker

@j_random_hacker Так, звичайно, читабельність важлива. Тести є формою документації і настільки ж важливі, як виробничий код. Я згоден, що тестування на важливі зміни - це добре (tm), і тести слід проводити на рівні інтерфейсу.
Джонно Нолан

19

Все повинно бути максимально простим, але не простішим. - А. Ейнштейн

Одне з найбільш неправильно зрозумілих речей про TDD - це перше слово в ньому. Тест. Тому й з’явився BDD. Тому що люди насправді не розуміли, що перший D - важливий, а саме Driven. Ми всі схильні думати трохи про тестування, а трохи про керування дизайном. І я припускаю, що це нечітка відповідь на ваше запитання, але ви, мабуть, повинні подумати, як керувати своїм кодом, замість того, що ви насправді тестуєте; у цьому вам може допомогти інструмент Покриття. Дизайн - це досить велике і проблематичне питання.


Так, це неясно ... Чи означає це, що як конструктор не є поведінкою частини, ми не повинні його тестувати. Але я повинен тестувати MyClass.DoSomething ()?
Джонно Нолан

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

Це рухомий розвиток, а не рухомий дизайн. Значення, отримати робочий базовий рівень, написати тести для перевірки функціональності, рухатися вперед із розвитком. Я майже завжди пишу свої тести безпосередньо перед тим, як вперше врахувати якийсь код.
Еван Плейс,

Я б сказав, що останнім D, Design, є слово, яке люди забувають, втрачаючи таким чином фокус. У тестовому дизайні ви пишете код у відповідь на невдалі тести. Якщо ви займаєтесь тестовим дизайном, скільки неперевіреного коду ви отримаєте?
sheldonh

15

Тим, хто пропонує перевірити "все": усвідомлюйте, що для "повного тестування" такого методу int square(int x)потрібно близько 4 мільярдів тестів на загальних мовах та типових середовищах.

Насправді, це навіть гірше , ніж: метод void setX(int newX)також зобов'язаний НЕ змінювати значення будь-яких інших членів , крім x- ви тестування , що obj.y, obj.zі т.д. все залишаються незмінними після виклику obj.setX(42);?

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


9

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

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


yup, і це прив'язка до класу з багатьма властивостями та багатьма конструкторами.
Джонно Нолан

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

5

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

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


5

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

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


4

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

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

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

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


3

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

Якщо у вас немає тесту на властивість, то навіщо вам його застосовувати? Якщо ви не протестуєте / не визначите очікувану поведінку у випадку "незаконного" призначення, що має робити властивість?

Тому я повністю перевіряю кожну поведінку, яку повинен проявляти клас. Включаючи "примітивні" властивості.

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

[TestFixture]
public class Test_MyObject_SomeProperty : PropertyTest<int>
{

    private MyObject obj = null;

    public override void SetUp() { obj = new MyObject(); }
    public override void TearDown() { obj = null; }

    public override int Get() { return obj.SomeProperty; }
    public override Set(int value) { obj.SomeProperty = value; }

    public override IEnumerable<int> SomeValidValues() { return new List() { 1,3,5,7 }; }
    public override IEnumerable<int> SomeInvalidValues() { return new List() { 2,4,6 }; }

}

Використовуючи лямбди та атрибути, це може бути навіть написано більш компактно. Я вважаю, MBUnit має навіть певну підтримку для подібних речей. Справа в тому, що наведений вище код фіксує намір властивості.

PS: Ймовірно, PropertyTest також повинен мати спосіб перевірити, щоб інші властивості об'єкта не змінювалися. Хм .. повертаємось до креслення.


Я відвідав презентацію на mbUnit. Це виглядає чудово.
Джонно Нолан

Але Девіде, дозвольте запитати вас: чи вас здивувала відповідь Кента Бека вище? Чи змушує його відповідь задуматися, чи не варто переосмислювати свій підхід? Звичайно, не тому, що хтось має "відповіді з висоти". Але Кента вважають одним із основних прихильників тестування першим. Пенні за ваші думки!
Charlie Flowers,

@Charly: Відповідь Кента дуже прагматична. Я "просто" працюю над проектом, де буду інтегрувати код з різних джерел, і я хотів би надати дуже високий рівень довіри.
Девід Шмітт,

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

1

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

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

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


1

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

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


0

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

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


0

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


0

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

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

Ви не сказали, чому також кодуєте архітектуру. Але для Java я використовую Maven 2 , JUnit , DbUnit , Cobertura та EasyMock .


Я не сказав, що це досить мовно-агностичне питання.
Джонно Нолан

Модульне тестування в TDD охоплює не тільки вас під час написання коду, але також захищає від людини, яка додає ваш код до коду, а потім вважає, що має сенс відформатувати значення всередині геттера!
Paxic

0

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

Коли вам потрібно перевірити, чи ваш тривіальний геттер насправді повертає правильне значення, це тому, що ви можете змішати ім'я геттера та ім'я змінної члена. Введіть 'attr_reader: name' ruby, і цього більше не може бути. Просто неможливо в Java.

Якщо ваш геттер коли-небудь стане нетривіальним, ви все одно можете додати тест на це тоді.


Я згоден, що тестування геттера є тривіальним. Однак я можу бути досить дурним, щоб забути встановити його в конструкторі. Тому необхідний тест. Мої думки змінилися з тих пір, як я задав питання. Дивіться мою відповідь stackoverflow.com/questions/153234/how-deep-are-your-unit-tests / ...
Johnno Нолан

1
Насправді, я стверджую, що певним чином модульні тести в цілому є запахом мовної проблеми. Мови, які підтримують контракти (умови попереднього / після методів), такі як Ейфель, все ще потребують певних модульних тестів, але їм потрібно менше. На практиці навіть прості контракти дозволяють дуже легко знаходити помилки: коли контракт методу розривається, помилка, як правило, знаходиться в цьому методі.
Damien Pollet

@Damien: Можливо, модульні тести та контракти насправді маскуються? Я маю на увазі, що мова, яка "підтримує" контракти, в основному полегшує написання фрагментів коду - тестів - які (необов'язково) виконуються до та після інших фрагментів коду, правильно? Якщо його граматика досить проста, мову, яка спочатку не підтримує контракти, можна легко розширити для підтримки, написавши препроцесор, правильно? Або є деякі речі, які один підхід (контракти чи модульні тести) може зробити, а інший просто не може?
j_random_hacker

0

Перевірте вихідний код, який вас турбує.

Не корисно тестувати ті частини коду, в яких ви дуже впевнені, якщо ви в цьому не помиляєтесь.

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

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

Тестуйте перед важким та середнім рефакторингом, щоб не порушити існуючі функції.


0

Ця відповідь скоріше для того, щоб з’ясувати, скільки одиничних тестів використовувати для даного методу, які ви знаєте, що хочете юніт-тести через його критичність / важливість. Використовуючи методику Basis Path Testing від McCabe, ви можете зробити наступне, щоб кількісно мати кращу впевненість у покритті коду, ніж просте "висвітлення виписки" або "покриття філій":

  1. Визначте значення цикломатичної складності вашого методу, який ви хочете одинично перевірити (наприклад, Visual Studio 2010 Ultimate може обчислити це для вас за допомогою інструментів статичного аналізу; в іншому випадку ви можете обчислити його вручну за допомогою методу потокової графіки - http://users.csc. calpoly.edu/~jdalbey/206/Lectures/BasisPathTutorial/index.html )
  2. Перелічіть базовий набір незалежних шляхів, що протікають через ваш метод - див. Посилання вище для прикладу потокової графіки
  3. Підготуйте модульні тести для кожного незалежного базового шляху, визначеного на кроці 2

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