Нове в одиничному тестуванні, як писати чудові тести? [зачинено]


267

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

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

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

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

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

Редагувати: Я хотів би подякувати Stack Overflow. У мене було чудово вкладено менше 15 хвилин, що відповіло на більшу кількість годин читання в Інтернеті, які я щойно робив.


1
Це найкраща книга для тестування одиниць: manning.com/osherove У ній роз'яснюються всі найкращі практики, тести та дози для тестування одиниць.
Ерві Б

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

Відповіді:


187

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

Я думаю, ти робиш це неправильно.

Тест одиниці повинен:

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

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

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

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

testAdd()
{
    int x = 5;
    int y = -2;
    int expectedResult = 3;
    Calculator calculator = new Calculator();
    int actualResult = calculator.Add(x, y);
    Assert.AreEqual(expectedResult, actualResult);
}

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


13
Велике спасибі, ваша відповідь була більш повною. Тепер я краще зрозумів, для чого насправді є макетні об’єкти: мені не потрібно стверджувати кожен виклик іншим методам, лише відповідним. Мені також не потрібно знати, як робити речі, але це правильно робити.
pixelastic

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

1
"Одиничний тест повинен перевірити один метод", я фактично не згоден. Тест одиниці повинен перевірити одне логічне поняття. Хоча це часто представляється одним методом, це не завжди так
robertmain

35

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

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

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

Усі найпростіші приклади, як function square(number)це чудово, - це, мабуть, погані кандидати, які витрачають багато часу на тестування. Ті, які мають важливу ділову логіку, саме там важливе тестування. Перевірте вимоги. Не випробовуйте сантехніку. Якщо вимоги змінюються, то вгадайте що, тести теж повинні.

Тестування не повинно бути буквально тестувати цю функцію для виклику функціональної панелі 3 рази. Це неправильно. Перевірте правильність результату та побічних ефектів, а не внутрішню механіку.


2
Гарна відповідь, що дав мені впевненість, що написання тестів за кодом все ще може бути корисним та можливим.
pixelastic

2
Ідеальний недавній приклад. У мене була дуже проста функція. Передайте це правдою, воно робить одне, помилкове - інше. ДУЖЕ ПРОСТО. Було як 4 перевірки тестування, щоб переконатися, що функція робить те, що має намір робити. Я трохи змінюю поведінку. Запустіть тести, проблема POW. Найцікавіше, що при використанні програми проблема не проявляється, це лише у складному випадку. Тестовий випадок знайшов це, і я врятував собі години головного болю.
Дмитро Лихтен

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

18

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

Переміщення існуючого коду до тестової керованої розробки

Я друге рекомендація книги прийнятої відповіді, але крім цього, у відповідях є більше інформації.


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

2
Імовірно, прийнята відповідь змінилася. Є відповідь від Linx, яка рекомендує мистецтво тестування одиниці Роя Ошерово, manning.com/osherove
thelem

15

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

Нехай ваші тести будуть невеликими: один тест на кожну вимогу.

Пізніше, коли вам потрібно внести зміни (або написати новий код), спробуйте спочатку написати один тест. Тільки один. Тоді ви зробите перший крок у розробці тестів.


Дякую, має сенс проводити лише невеликі тести з невеликою потребою, по одному. Заняття.
пікселастик

13

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


Дякую ! У мене було відчуття, що я роблю це неправильно, але краще, якщо хтось насправді скаже мені, краще.
pixelastic

8

Спробуйте написати модульний тест перед тим, як написати метод, який він буде тестувати.

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

Ви завжди повинні протестувати результати методу, а не те, як метод отримує ці результати.


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

2
@pixelastic роблять вигляд, що методи не були написані?
committedandroider

4

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

When I'm writing tests for a method, I have the feeling of rewriting a second time what I          
already wrote in the method itself.
My tests just seems so tightly bound to the method (testing all codepath, expecting some    
inner methods to be called a number of times, with certain arguments), that it seems that
if I ever refactor the method, the tests will fail even if the final behavior of the   
method did not change.

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


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

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

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