За межами відсотка покриття: тестування проблем, з якими фактично стикаються користувачі
Чому високий показник охоплення коду може приховувати неперевірені способи збою, три види формальних тестів, які він спонукає до використання, та як писати тести, що захищають справжню поведінку програми.
Запит на з’єднання, у якому всі тести пройшли успішно, а звіт про покриття становить понад 98%, здається надійним. Але потім API повертає null, коли інтерфейс очікував об’єкт, запит виконується не у правильному порядку через повільне мобільне з’єднання, або хтось подвійно клацає кнопку надсилання, і фронтенд перетворюється на порожній екран. Питання після аналізу причин завжди одне: як це могло зламатися, якщо файл був повністю протестований? У цій статті пояснюється, що насправді вимірює покриття, які шаблони тестування його завищують без додавання захисту, та як скерувати набір тестів на виявлення справді важливих помилок.
Що вимірює покриття, а що — ні
Інструмент вимірювання охоплення фіксує, які рядки, гілки та функції виконувалися під час запуску тестів. Це все. Він не знає, чи перевіряла якась твердження результат, чи вхідні дані нагадували реальний трафік, чи код поводиться правильно, коли щось у залежностях працює некоректно. Виконання та перевірка — це дві різні характеристики, і інструмент вимірювання охоплення повідомляє лише про першу.
Корисною аналогією є перевірка будівлі, під час якої хтось обходить кожну кімнату з ліхтариком. Кожна кімната була оглянута, проте ніхто не перевіряв дах під час шторму. Високе охоплення свідчить про те, що ваші тести охопили код, але не про те, що вони його протестували на міцність.
Три патерни, які збільшують охоплення без покращення надійності
Коли ціль стає відсотком, люди оптимізуються саме під цей відсоток. У результаті утворюються тести, які задовольняють інструмент з мінімальних зусиль. Постійно з’являються три певні патерни.
Міраж ідеального сценарію
undefined, повільну відповідь чи неправильно сформований JSON. Кожен кодовий рядок виконувався, але не було протестовано жодних вхідних даних, які можуть спричинити проблеми в реальних умовах.
Компонент з надмірним мокуванням
Тут кожен виклик API, постачальник контексту та вкладені елементи замінюються на штучні аналоги. Тестування завершується за кілька мілісекунд та охоплює всі можливі сценарії відображення. Однак у реальних умовах справжній кінець шляху повертає дані у трохи іншій формі, ніж передбачалося в моку, або бібліотека змінює спосіб генерації подій після оновлення. Тести продовжують проймати, тому що вони спілкуються лише з власними припущеннями, а справжня інтеграція відбувається у браузері користувача.
Тести без значущих перевірок
Найслабша форма проявляється під час суворих вимог, наприклад, обов’язкового порогу у 90% серед усієї команди. Тести викликають функції лише для фіксації виконання операцій та майже не перевіряють щось. Звіт стає зеленим, хоча між кодом та майбутніми змінами логіки немає жодних перевірок.
Як тестувати поведінку замість кількості рядків
Тести, які виявляють помилки раніше, ніж це зроблять користувачі, зосереджуються на тому, що робить система, а не на тих рядках коду, які вона обробляє.
- Тестувати потрібно стани та переходи, а не окремі функції. Користувачам не важливо, чи виконався певний допоміжний код. Їх цікавить те, що відбувається, коли з’єднання інтернету переривається під час надсилання форми, або коли вони намагаються покинути сторінку, поки триває завантаження файлу. Необхідно перевіряти індикатори завантаження, стани помилок, можливості повторної спроби та механізми обробки помилок.
- Використовувати інтеграцію там, де це практично доцільно. Імітація справжніх зовнішніх компонентів, таких як платіжний шлюз сторонньої компанії, є розумною. Імітація власних внутрішніх допоміжних функцій чи шару даних переважно приховує помилки, які існують між ними. Дозвольте компонентам та модулям працювати разом наскільки це дозволяють умови.
Пов’язаною та добре відомою технікою є тестування мутацій, яке навмисно змінює ваш код (змінює умову, видаляє рядок) та перевіряє, чи якісь тести завершуються невдачею. Мутації, які виживають, вказують безпосередньо на код, який виконується, але не перевіряється, що саме і не може показати тестування охоплення. Щодо деталей на рівні компонентів, дивіться ці антипатерни тестування React, які створюють хибну впевненість.
Використання охоплення як сигналу, а не як мети
Рівень покриття не є марним. Низький показник у критичному модулі є справжнім попередженням, а звіт може виявити шляхи виконання коду, які зовсім ніхто не тестує. Проблеми починаються тоді, коли цей відсоток стає критерієм, адже це заохочує кількість тестів більше, ніж їхню якість.
Ефективний набір тестів із рівнем покриття близько 65%, який зосереджується на ризикованих процесах, складних бізнес-правилах та механізмах відновлення після збоїв, допоможе запобігти набагато більшій кількості інцидентів, ніж нестабільний набір тестів із рівнем покриття 95%, створений на основі безпроблемних сценаріїв та моків. Перш ніж додавати тест, краще запитати: «Який реалістичний збій цей тест виявить?», ніж «Які рядки залишилися поза тестуванням?»
Ключові висновки
- Рівень покриття показує, який код було виконано, а не чи перевірялась його поведінка.
- Вхідні дані з безпроблемних сценаріїв, інтенсивне використання внутрішніх моків та тести без перевірок призводять до підвищення показника покриття без зниження ризиків.
Пов’язана література
- Beyond Bundle Size: Finding What Actually Makes Your Web App Slow — Чому скорочення кілобайтів рідко допомагає вирішити проблему повільності додатку та як відстежувати справжній час очікування між серверами, етапами обробки даних, скриптами сторонніх постачальників та зображеннями.
- Beyond P95: Вимірювання затримки, яку насправді переживають ваші користувачі — чому нормальне значення P95 може існувати разом із повільним продуктом, як час очікування в черзі та розподіл завдань залишаються непоміченими на панелях керування, та як вимірювання часу для кожного кроку покладає край суперечкам щодо причин затримки. —