Як ви пишете одиничні тестові приклади?


14

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

Яка нормальна тенденція? Чи повинні розробники писати одиничні тестові справи для коду, який вони написали самі?


2
"дріт"? Що означає "барвник"?
S.Lott

Відповіді:


12

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

Але в Інтернеті є незліченна кількість інших.

У прямій відповіді на ваші запитання ...

  1. "Звичайна тенденція" - я думаю, що це може відрізнятися від місця до місця, те, що для мене нормально, може бути дивним для інших.
  2. Я б сказав (на мій варіант) розробник, який пише код, повинен написати тест, в ідеалі використовуючи такі методи, як TDD, де ви писали б тест перед кодом. Але інші можуть мати різні методи та ідеї тут!

І те, як ви описали написання тестів (у своєму запитанні), абсолютно неправильне !!


9

Такий підхід робить тест одиниці нікчемним.

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


8
Це не зовсім так. А точніше, це правда в ідеальному світі, але, на жаль, часто ми далекі від цього. Подумайте про те , успадкованому код без тестів і без специфікацій, і без тих , хто міг би надійно сказати вам , до найдрібніших деталей, що робить певний шматок коду саме повинен робити (це є реальність в значній частині існуючих проектів). Навіть у цьому випадку, можливо, варто все-таки написати одиничні тести, щоб заблокувати поточний стан коду та переконатися, що ви нічого не порушите з майбутнім рефакторингом, виправленнями помилок чи розширеннями.
Péter Török

2
Крім того, я думаю, ви мали на увазі "написати тест після коду для тестування"?
Péter Török

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

ørn, якщо ви маєте на увазі, що ми повинні мати змістовні твердження в наших одиничних тестах, щоб переконатися, що протестований код справді робить те, що ми думаємо, що це робить, я цілком згоден.
Péter Török

3

Якщо ви не знаєте, що робить функція, то ви не можете написати тест для неї. Для всіх, хто знає, він навіть не робить те, що належить. Вам потрібно з’ясувати, що це потрібно зробити спочатку. ТАКІ напишіть тест.


3

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

Запитайте себе: чому ми пишемо одиничні тести? Йти Гріном, очевидно, є лише засобом для досягнення мети, кінцева мета - довести або спростувати твердження про тестований код.

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

public double squareRoot(double number);

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

  1. що він може повернути прості корені, як sqrt (4.0)
  2. що він може знайти справжній корінь типу sqrt (2.0) з розумною точністю
  3. що він знаходить, що sqrt (0,0) дорівнює 0,0
  4. що він видає IllegalArgumentException, коли подається від'ємне число, тобто на sqrt (-1.0)

Отже, ви починаєте писати їх як індивідуальні тести:

@Test
public void canFindSimpleRoot() {
  assertEquals(2, squareRoot(4), epsilon);
}

На жаль, цей тест вже не працює:

java.lang.AssertionError: Use assertEquals(expected, actual, delta) to compare floating-point numbers

Ви забули про арифметику з плаваючою точкою. Гаразд, ти представиш double epsilon=0.01і підеш:

@Test
public void canFindSimpleRootToEpsilonPrecision() {
  assertEquals(2, squareRoot(4), epsilon);
}

і додайте інші тести: нарешті

@Test
@ExpectedException(IllegalArgumentException.class)
public void throwsExceptionOnNegativeInput() {
  assertEquals(-1, squareRoot(-1), epsilon);
}

і ой, знову:

java.lang.AssertionError: expected:<-1.0> but was:<NaN>

Ви повинні були протестувати:

@Test
public void returnsNaNOnNegativeInput() {
  assertEquals(Double.NaN, squareRoot(-1), epsilon);
}

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

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

@Test
public void throwsNoExceptionOnNegativeInput() {
  assertNotNull(squareRoot(-1)); // Shouldn't this fail?
}

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

Вам слід спробувати вийти з розуму, що ви повинні писати одиничні тести лише тому, що вам доведеться, як і ви заповнювали обов'язкові поля у формі. І не слід писати одиничні тести лише для того, щоб червона лінія стала зеленою. Одиничні тести не ваші вороги, одиничні тести - ваші друзі.


1

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

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


1

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

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

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