Тестування проти не повторюватися (DRY)


11

Чому повторюється, пишучи тести, настільки заохочується?

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

Відповіді:


25

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

Тестовий код, який тестує код виробництва, зовсім не схожий. Я продемонструю в python:

def multiply(a, b):
    """Multiply ``a`` by ``b``"""
    return a*b

Тоді простим тестом було б:

def test_multiply():
    assert multiply(4, 5) == 20

Обидві функції мають подібне визначення, але обидві виконують дуже різні речі. Тут немає дублікату коду. ;-)

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

def test_multiply_1_and_3():
    """Assert that a multiplication of 1 and 3 is 3."""
    assert multiply(1, 3) == 3

def test_multiply_1_and_7():
    """Assert that a multiplication of 1 and 7 is 7."""
    assert multiply(1, 7) == 7

def test_multiply_3_and_4():
    """Assert that a multiplication of 3 and 4 is 12."""
    assert multiply(3, 4) == 12

Уявіть, що ви робите це для 1000+ ефективних рядків коду. Замість цього ви протестуєте за принципом "особливості":

def test_multiply_positive():
    """Assert that positive numbers can be multiplied."""
    assert multiply(1, 3) == 3
    assert multiply(1, 7) == 7
    assert multiply(3, 4) == 12

def test_multiply_negative():
    """Assert that negative numbers can be multiplied."""
    assert multiply(1, -3) == -3
    assert multiply(-1, -7) == 7
    assert multiply(-3, 4) == -12

Тепер, коли функції додаються / видаляються, я повинен розглянути лише додавання / видалення однієї тестової функції.

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


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

@ pink-diamond-square Я бачу, що NUnit не припиняє тестувати після того, як твердження провалиться (що, на мою думку, є дивним). У цьому конкретному випадку дійсно краще мати одне твердження за тест. Якщо рамка тестування одиниць припиняє тестування після невдалого твердження, то кілька тверджень краще.
siebz0r

3
NUnit не зупиняє весь тестовий набір, але цей тест припиняється, якщо ви не вживаєте заходів для його запобігання (ви можете зловити виняток, який він викидає, що іноді корисно). Я думаю, що вони роблять те, що якщо ви пишете тести, що містять більше одного твердження, ви не отримаєте всієї інформації, необхідної для виправлення проблеми. Щоб проаналізувати ваш приклад, уявіть, що ця функція множення не подобається цифрі 3. У цьому випадку assert multiply(1,3)це не вдасться, але ви також не отримаєте звіт про невдалий тест assert multiply(3,4).
Церква Роб

Я просто думав, що підніму це, тому що одне твердження за тест - це те, що я читав у світі .net, "хороша практика" та кілька тверджень - "прагматичне використання". Це виглядає дещо інакше в документації Python, де приклад def test_shuffleвиконує два твердження.
Церква Роб

Я згоден і не згоден: D Тут чітко повторення: assert multiply(*, *) == *ви можете визначити assert_multiplyфункцію. У поточному сценарії це не має значення за кількістю рядків та читабельністю, але при більш тривалих тестах ви можете повторно використовувати складні твердження, світильники, код генерації кріплення тощо. Я не знаю, чи це найкраща практика, але зазвичай я це.
inf3rno

10

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

Ні, це неправда.

Тести мають інше призначення, ніж ваша реалізація:

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

4

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


2

Невже кінцева мета DRY не включатиме усунення всього тестового коду?

Ні, кінцева мета DRY фактично означатиме усунення всього виробничого коду .

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

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

Я не думаю, що зворотне (позбавлення від усіх тестів) бажано, оскільки:

  • Ви повинні вирішити невідповідність імпедансу між реалізацією та специфікацією. Виробничий код може передати наміри до певної міри, але це ніколи не буде так просто міркувати про добре виражені тести. Нам людям потрібен вищий погляд на те, чому ми будуємо речі. Навіть якщо ви не зробите тести через DRY, специфікації, ймовірно, доведеться записати в документи, що, безумовно, є більш небезпечним звіром з точки зору невідповідності імпедансу та десинхронізації коду, якщо ви запитаєте мене.
  • Незважаючи на те, що виробничий код можна легко отримати за допомогою правильних виконуваних специфікацій (за умови достатнього часу), тестовий набір значно складніше відновити з кінцевого коду програми. Технічні характеристики не виглядають чітко, просто дивлячись на код, тому що взаємодія між кодовими одиницями під час виконання складно простежити. Ось чому у нас так важко розбиратися з безперебійними застарілими програмами. Іншими словами: якщо ви хочете, щоб ваша програма вижила більше декількох місяців, вам буде краще втратити жорсткий диск, на якому розміщена ваша виробнича кодова база, ніж той, де знаходиться ваш тестовий набір.
  • Набагато простіше ввести помилку у виробничий код, ніж у тестовому коді. А оскільки виробничий код не є самоперевіреним (хоча до цього можна підходити з Design by Contract або системами більшого типу), нам все-таки потрібна якась зовнішня програма, щоб перевірити її та попередити нас, якщо відбудеться регресія.

1

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


1

Оскільки тестування на одиницях полягає у посиленні ненавмисних змін , іноді вони також можуть посилити навмисні зміни . Цей факт справді пов'язаний з принципом DRY.

Наприклад, якщо у вас є функція, MyFunctionяка викликається у виробничому коді лише в одному місці, і ви пишете для неї 20 одиничних тестів, ви можете легко отримати в своєму коді 21 місце, де ця функція називається. Тепер, коли вам доведеться змінити підпис MyFunctionабо семантику, або і те й інше (оскільки деякі вимоги змінюються), у вас є 21 місце для зміни замість одного. І причина справді - порушення принципу DRY: ви повторили (принаймні) одну і ту ж функцію дзвінка MyFunction21 раз.

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

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


0

Однією з найбільших проблем у створенні програмного забезпечення є захоплення вимог; тобто відповісти на запитання "що робити це програмне забезпечення?" До програмного забезпечення потрібні точні вимоги, щоб точно визначити, що система повинна робити, але ті, хто визначає потреби в програмних системах та проектах, часто включають людей, які не мають програмного чи формального (математичного) досвіду. Відсутність суворості у визначенні вимог змусило розробку програмного забезпечення знайти спосіб перевірити програмне забезпечення відповідно до вимог.

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

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

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

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


0

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


0

Одиничні тести не повинні включати дублювання тестуваного коду, як уже зазначалося.

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

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

Місцевий євангеліст TDD / BDD висловлюється так:
"Ваш виробничий код повинен бути СУХОМ. Але це нормально, щоб ваші тести були" вологими "."


0

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

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

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

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