Проектування одиничних тестів для стабільної системи


20

Фон

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

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

Джерело: http://www.artima.com/weblogs/viewpost.jsp?thread=35578

Вони також кажуть подібні речі (далі - принцип приватного методу) або PMP :

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

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

Джерело: Як ви використовуєте тестування приватних методів?

Ситуація

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

  • SAP пропонує мені не тестувати "процедуру нарощування стану", я повинен припустити, що стан - це те, що я очікую від коду нарощування, а потім перевірити зміну одного стану, яку я намагаюся перевірити

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

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


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


@Doval: Поясніть, будь ласка, як зробити щось на зразок телефону (SIP UserAgent) недержавним. Очікувана поведінка цього пристрою задається в RFC за допомогою діаграми переходу стану.
Барт ван Інген Шенау

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

Щодо дотичної, але я також додам, що діаграми стану є інструментом зв'язку, а не декретом про реалізацію, навіть якщо він знаходиться в RFC. Поки ви дотримуєтесь описаної функціональності, ви відповідає стандарту. У мене було кілька випадків, коли я перетворював дійсно складні реалізації переходу стану (як визначено в RFC) в дійсно просту загальну функціональність обробки. Один випадок, коли я пам’ятаю, як позбувся пари тисяч рядків коду, як тільки зрозумів, що крім пари прапорів про 5 держав було зроблено саме те саме, коли ви перейменували «приховані» загальні елементи.
Данк

Відповіді:


15

Перспектива:

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

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

Принципи - SAP:

Хоча я не є експертом в області TDD, я думаю, що ви пропускаєте частину того, що намагається навчати Принцип єдиного твердження (SAP). SAP можна відновлювати як "перевірити одну за одною". Але TOTAT не скочує язик так легко, як SAP.

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

Якщо ви перевіряєте одну за одною, обсяг пошуку значно менший, а дефект виявляється швидше. Майте на увазі, що "тестування однієї речі за часом" не обов'язково виключає перегляд декількох результатів процесу одночасно. Наприклад, під час тестування "відомого хорошого шляху" я можу розраховувати побачити конкретне, отримане значення foo, а також інше значення в, barі я можу перевірити це foo != barяк частину свого тесту. Головне - логічно групувати вихідні перевірки на основі тестуваного випадку.

Принципи - PMP:

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

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


Застосований TDD ( для вас )

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

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

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

Далі вам потрібно створити тести для перевірки обробки даних. Деякі з цих тестів стану будуть повторно використані, коли ви створюєте тести обробки даних. Наприклад, припустимо, у вас є метод, Foo()який має різні результати, засновані на а Initта State1станах. Ви хочете використовувати свій ChangeFooToState1тест як етап налаштування, щоб перевірити вихід, коли " Foo()знаходиться State1".

За цим підходом є деякі наслідки, які я хочу зазначити. Спойлер, тут я розлючую пуристів

По-перше, ви повинні прийняти, що ви використовуєте щось як тест в одній ситуації та налаштування в іншій ситуації. З одного боку, це здається прямим порушенням SAP. Але якщо ви логічно уявляєте ChangeFooToState1, що маєте дві цілі, то ви все ще зустрічаєтеся з духом того, чого навчає нас SAP. Коли вам потрібно переконатися в Foo()змінах стану, ви використовуєте ChangeFooToState1як тест. І коли потрібно перевірити Foo()висновок " ", коли State1", то ви використовуєте його ChangeFooToState1як налаштування.

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

Поєднання разом:

Використовуючи діаграму стану, ви генеруєте тести для покриття переходів. Знову ж, використовуючи свою діаграму, ви генеруєте тести для покриття всіх випадків обробки даних введення / виводу, керованих станом.

Якщо ви дотримуєтесь цього підходу, bloated, complicated, long, and difficult to writeтести повинні бути трохи простішими в управлінні. Загалом вони повинні бути меншими, і вони повинні бути більш короткими (тобто менш складними). Слід зазначити, що тести також є більш відокремленими або модульними.

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


11

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

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

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

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

Іншими словами, ви не можете уникнути стану взагалі, але ви можете мінімізувати та ізолювати його.


Тільки для запису, приклад SIP був мій, а не з ОП. А для деяких машин стану може знадобитися більше декількох викликів методів, щоб перевести їх у правильний стан для певного тесту.
Барт ван Інген Шенау

+1 для "не можна взагалі уникнути стану, але ви можете мінімізувати та ізолювати його". Я не могла погодитися. Держава - це необхідне зло в програмному забезпеченні.
Брендон

0

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

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

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

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