Чи дублюється код більш терпимим в одиничному тесті?


113

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

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

Відповіді:


68

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

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

Якщо дублювання міститься в коді, що маніпулює SUT, то запитайте себе, чому декілька так званих «одиничних» тестів виконують саме таку функціональність.

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

assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())

Тоді, можливо, вам потрібен єдиний assertPersonEqualметод, щоб ви могли писати assertPersonEqual(Person('Joe', 'Bloggs', 23), person). (Або, можливо, вам просто потрібно перевантажити оператора рівності Person.)

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

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


30
"Дублюваний код - це запах в одиничному тестовому коді так само, як і в іншому коді." Ні. "Якщо ви скопіювали код у тестах, це ускладнює переформатування коду реалізації, оскільки у вас є непропорційна кількість тестів для оновлення." Це відбувається тому, що ви тестуєте приватний API замість загальнодоступного API.

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

@ user11617, будь ласка, визначте "приватний API" та "public Api". Наскільки я розумію, публічний Api - це Api, який видимий для зовнішнього світу / сторонніх споживачів і явно переосмислений через SemVer або подібне, все інше є приватним. З цим визначенням майже всі тести Unit тестують "приватний API" і, отже, більш чутливі до дублювання коду, що, на мою думку, є правдою.
KolA

@KolA "Громадське" не означає сторонніх споживачів - це не веб-API. Загальнодоступний API класу відноситься до методів, призначених для використання клієнтським кодом (який, як правило, не повинен / сильно змінюється) - як правило, "загальнодоступні" методи. Приватний API посилається на логіку та методи, які використовуються всередині. До них не можна звертатися поза класом. Це одна з причин, що важливо правильно інкапсулювати логіку в класі, використовуючи модифікатори доступу або конвенцію на мові, що використовується.
Натан

@Якщо будь-яка бібліотека / dll / nuget пакунок має сторонніх споживачів, це не повинно бути веб-програмою. Те, про що я згадував, - це те, що оголошувати публічні класи та учасників, які не повинні використовуватись безпосередньо споживачами бібліотеки (або, в кращому випадку, робити їх внутрішніми та коментувати складання за допомогою InternalsVisibleToAttribute), дуже часто, щоб дозволити одиничним тестам безпосередньо їх дістати. Це призводить до купи тестів у поєднанні з впровадженням і робить їх більш тягарем, ніж перевагою
KolA

186

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

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


xUnit та інші містять аргумент 'message' у викликах затвердження. Хороша ідея скласти змістовні фрази, щоб дозволити розробникам швидко знайти невдалі результати тесту.
seand

1
@seand Ви можете спробувати пояснити, що перевіряє ваш арбітр, але коли він не працює і містить дещо затемнений код, тоді розробнику потрібно буде все-таки перейти і розкрутити його. ІМО Важливо мати код, щоб там самоописуватись.
ІгорК

1
@Kristopher,? Чому це повідомлення - це вікі спільноти?
Печер'є

@Pacerier Я не знаю. Раніше були складні правила, коли речі автоматично стають вікі спільноти.
Крістофер Джонсон

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

47

Код впровадження та тести - це різні тварини, і правила факторингу щодо них по-різному.

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

З іншого боку, код тестування повинен підтримувати рівень дублювання. Копіювання тестового коду досягає двох цілей:

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

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

Коли дублювання повзає в частині тестів "перевірити", часто вигідно визначити власні методи твердження. Зрозуміло, ці методи все ж повинні перевірити чітко визначене співвідношення, яке може бути зрозумілим у назві методу: assertPegFitsInHole-> добре, assertPegIsGood-> погано.

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

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


8

Я згоден. Торгівля існує, але в різних місцях є різною.

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


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

8

Можна зменшити повторення, використовуючи кілька різних ароматів тестових корисних методів .

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


6

Джей Філдс придумав фразу про те, що "DSL повинні бути DAMP, а не DRY", де DAMP означає описові та змістовні фрази . Я думаю, те саме стосується і тестів. Очевидно, занадто багато дублювання - це погано. Але видалення дублювання будь-якою ціною ще гірше. Тести повинні діяти як специфікації, що розкривають наміри. Якщо, наприклад, ви вказуєте ту саму особливість з декількох різних ракурсів, то слід очікувати певної кількості дублювання.


3

Я люблю rspec через це:

У ньому є дві речі, які допоможуть -

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

  • вкладені контексти.
    Ви можете мати метод "setup" та "teardown" для певного підмножини тестів, а не для кожного класу.

Чим раніше .NET / Java / інші тестові рамки приймуть ці методи, тим краще (або ви могли використовувати IronRuby або JRuby для написання своїх тестів, що я особисто вважаю, що це кращий варіант)


3

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

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

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


2

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


2

В ідеалі одиничні тести не повинні сильно змінюватися після їх написання, щоб я схилявся до читабельності.

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

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


2

"відновив їх знову, щоб зробити їх сухими - намір кожного тесту вже не був зрозумілий"

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

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

За старих часів у нас були інструменти тестування, які використовували різні мови програмування. Було важко (або неможливо) розробити приємні, прості у роботі тести.

У вас є вся потужність - якою б мовою ви не користувалися - Python, Java, C # - тому добре використовуйте цю мову. Ви можете досягти чіткого і не надто зайвого тестового коду. Немає компромісів.

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