Відповіді:
Я вважаю, що це неправильне уявлення про будь-який спосіб, що я можу придумати.
Тестовий код, який тестує код виробництва, зовсім не схожий. Я продемонструю в 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
петлі. Це тому, що повторювати деякі речі добре. Коли я застосував би петлі, код був би набагато коротшим. Але коли твердження не вдається, воно може приховати висновок, показуючи неоднозначне повідомлення. Якщо це відбувається , то ваші тести будуть менш корисні , і ви будете мати потребу в отладчике , щоб перевірити , де справи йдуть погано.
assert multiply(1,3)
це не вдасться, але ви також не отримаєте звіт про невдалий тест assert multiply(3,4)
.
def test_shuffle
виконує два твердження.
assert multiply(*, *) == *
ви можете визначити assert_multiply
функцію. У поточному сценарії це не має значення за кількістю рядків та читабельністю, але при більш тривалих тестах ви можете повторно використовувати складні твердження, світильники, код генерації кріплення тощо. Я не знаю, чи це найкраща практика, але зазвичай я це.
Здається, тести в основному виражають те саме, що і код, а значить, це дублікат
Ні, це неправда.
Тести мають інше призначення, ніж ваша реалізація:
Невже кінцева мета DRY не включатиме усунення всього тестового коду?
Ні, кінцева мета DRY фактично означатиме усунення всього виробничого коду .
Якщо наші тести можуть бути ідеальними специфікаціями того, що ми хочемо зробити, нам просто доведеться автоматично генерувати відповідний виробничий код (або бінарні файли), ефективно видаляючи базовий код виробництва.
Це власне те, на що претендують такі підходи, як архітектура, орієнтована на модель, - єдине людське джерело істини, з якого все походить шляхом обчислення.
Я не думаю, що зворотне (позбавлення від усіх тестів) бажано, оскільки:
Оскільки тестування на одиницях полягає у посиленні ненавмисних змін , іноді вони також можуть посилити навмисні зміни . Цей факт справді пов'язаний з принципом DRY.
Наприклад, якщо у вас є функція, MyFunction
яка викликається у виробничому коді лише в одному місці, і ви пишете для неї 20 одиничних тестів, ви можете легко отримати в своєму коді 21 місце, де ця функція називається. Тепер, коли вам доведеться змінити підпис MyFunction
або семантику, або і те й інше (оскільки деякі вимоги змінюються), у вас є 21 місце для зміни замість одного. І причина справді - порушення принципу DRY: ви повторили (принаймні) одну і ту ж функцію дзвінка MyFunction
21 раз.
Правильний підхід у такому випадку полягає у застосуванні принципу DRY до вашого тестового коду: коли ви пишете 20 тестових одиниць, інкапсулюйте виклики MyFunction
в тести свого блоку лише за допомогою декількох допоміжних функцій (в ідеалі - лише однієї), які використовуються 20 одиничних тестів. В ідеалі, у вашому коді є лише два місця MyFunction
: одне з вашого виробничого коду та одне з ваших одиничних тестів. Отже, коли вам доведеться змінити підпис MyFunction
пізніше, у вас буде лише декілька місць, щоб змінити свої тести.
"Кілька місць" - це все-таки більше, ніж "одне місце" (те, що ви отримуєте взагалі без одиничних тестів), але переваги наявності одиничних тестів повинні сильно переважати за перевагу того, що менше коду для зміни (інакше ви робите тестування одиниць повністю. неправильно).
Однією з найбільших проблем у створенні програмного забезпечення є захоплення вимог; тобто відповісти на запитання "що робити це програмне забезпечення?" До програмного забезпечення потрібні точні вимоги, щоб точно визначити, що система повинна робити, але ті, хто визначає потреби в програмних системах та проектах, часто включають людей, які не мають програмного чи формального (математичного) досвіду. Відсутність суворості у визначенні вимог змусило розробку програмного забезпечення знайти спосіб перевірити програмне забезпечення відповідно до вимог.
Команда розробників виявила переклад розмовного опису проекту на більш жорсткі вимоги. Дисципліна тестування перетворилася на контрольну точку розробки програмного забезпечення, щоб усунути розрив між тим, що клієнт каже, що хоче, і тим, що програмне забезпечення розуміє, що хоче. І розробники програмного забезпечення, і команда з якості / тестування формують розуміння (неофіційної) специфікації, і кожен (незалежно) пише програмне забезпечення або тести, щоб забезпечити їх розуміння. Додавання іншої людини для розуміння (неточних) вимог додало запитань та різну точку зору для подальшого відточення точності вимог.
Оскільки завжди було прийняття тестування, було природно розширити роль тестування, щоб написати автоматизовані та одиничні тести. Проблема полягала в тому, що найняли програмістів для тестування, і таким чином ви звузили перспективу від забезпечення якості до програмістів, які роблять тестування.
Все, що ви сказали, ви, ймовірно, робите тест неправильно, якщо ваші тести мало відрізняються від реальних програм. Пропозиція Msdy полягає в тому, щоб більше зосередитися на тестах, а менше - на тому, як.
Іронія полягає в тому, що замість того, щоб зафіксувати формальну специфікацію вимог у розмовному описі, галузь вирішила застосувати точкові тести як код для автоматизації тестування. Замість того, щоб випускати формальні вимоги, на які можна було б створити відповідне програмне забезпечення, застосовуваний підхід був перевірити кілька моментів, а не підходити до створення програмного забезпечення з використанням формальної логіки. Це компроміс, але був досить ефективним і відносно успішним.
Якщо ви вважаєте, що ваш тестовий код занадто схожий на код вашої реалізації, це може бути свідченням того, що ви надмірно використовуєте глузливі рамки. Макетне тестування на занадто низькому рівні може призвести до того, що тестова установка буде дуже схожа на метод, який тестується. Постарайтеся написати випробування вищого рівня, які мають меншу ймовірність зламатися, якщо ви зміните свою реалізацію (я знаю, що це може бути важко, але якщо ви зможете цим керувати, у вас з'явиться більш корисний набір тестів).
Одиничні тести не повинні включати дублювання тестуваного коду, як уже зазначалося.
Я хотів би додати, що одиничні тести, як правило, не такі сухі, як "виробничий" код, тому що налаштування має тенденцію бути схожим (але не ідентичним) у тестах ... особливо якщо у вас є значна кількість залежностей, над якими ви знущаєтеся / підробка.
Звичайно, можна переробити цей вид речі в загальний метод налаштування (або набір методів настройки) ... але я виявив, що ці методи налаштування мають, як правило, довгі списки параметрів і досить крихкі.
Тож будьте прагматичними. Якщо ви можете консолідувати код налаштування без шкоди для ремонту, будь ласка, зробіть це. Але якщо альтернативою є складний і крихкий набір методів настройки, трохи повторення у ваших методах тестування нормально.
Місцевий євангеліст TDD / BDD висловлюється так:
"Ваш виробничий код повинен бути СУХОМ. Але це нормально, щоб ваші тести були" вологими "."
Здається, тести в основному виражають те саме, що і код, і, отже, це дублікат (в принципі, а не реалізація) коду.
Це неправда, тести описують випадки використання, тоді як код описує алгоритм, який передає випадки використання, так що є більш загальним. За допомогою TDD ви починаєте писати випадки використання (ймовірно, виходячи з історії користувача), після чого реалізуєте код, необхідний для передачі цих випадків використання. Отже, ви пишете невеликий тест, невеликий шматок коду, а після цього рефактор, якщо потрібно, щоб позбутися від повторів. Ось як це працює.
За допомогою тестів можуть бути і повтори. Наприклад, ви можете повторно використовувати світильники, генеруючи код кріплення, складні твердження тощо ... Я зазвичай це роблю, щоб запобігти помилкам в тестах, але зазвичай спочатку забуваю перевірити, чи справді тест не виходить з ладу, і він може справді зіпсувати день , коли ви шукаєте помилку в коді протягом півгодини, і тест помиляється ... xD