Класи еквівалентності
Зміст
Вичерпне тестування неможливе — це не гасло з підручника, а проста арифметика. Поле «вік» з допустимим діапазоном 18–65 приймає безліч можливих введень: усі цілі числа, дроби, відʼємні значення, букви, порожній рядок, пробіли, емодзі. Перебрати все — нереально; тицьнути навмання три значення — страшно, бо незрозуміло, що саме ти перевірив і що пропустив. Розбиття на класи еквівалентності (equivalence partitioning) — перша техніка тест-дизайну, яка перетворює цей хаос на короткий список тестів із зрозумілою логікою: чому саме ці значення і чому їх достатньо.
На співбесідах це стандарт де-факто: майже кожне «протестуйте поле вводу» — замаскована перевірка, чи вмієш ти ділити дані на класи й рахувати мінімум тестів. У ланцюжку test basis → тестова умова → тест-кейс, який ми розібрали в главі про шлях від вимоги до перевірки, класи еквівалентності — найпростіший крок від умови до конкретних значень у кейсі.
Ідея розбиття
Механізм такий: усі можливі значення ділимо на групи — класи еквівалентності — так, щоб за специфікацією система обробляла кожне значення всередині класу однаково. Тоді з кожного класу достатньо взяти одного представника. Якщо тест із представником пройшов — вважаємо, що пройшли б і решта значень класу; якщо впав — упав би весь клас. Саме ця гіпотеза еквівалентності й дає економію: замість тисяч значень — по одному на клас.
Щоб розбиття було коректним, воно має відповідати двом правилам:
- Класи не перетинаються — кожне значення належить рівно одному класу. Якщо значення 65 потрапляє і в «валідний діапазон», і в «занадто великі», межі класів визначені неохайно.
- Класи покривають усе — будь-яке можливе введення має свій клас. Не тільки числа: рядок «abc», порожнє поле і пробіли теж кудись мають впасти.
Для поля «вік 18–65» розбиття виглядає так:
Покриття технікою рахується прозоро: скільки класів закрито тестами, поділити на загальну кількість класів. Чотири класи — мінімум чотири тести, і ти можеш аргументувати цю цифру, а не «ну, я перевірив кілька значень».
Важливе чесне застереження: «обробляються однаково» — це гіпотеза за специфікацією, а не факт про код. Реалізація може ділити діапазон інакше, ніж написано у вимогах, і найчастіше розбіжності живуть на межах класів — там, де 17 стає 18. Тому класи еквівалентності майже ніколи не застосовують окремо: їхній природний напарник — аналіз граничних значень (boundary value analysis), який перевіряє саме краї кожного класу.
Валідні й невалідні класи
Класи ділять на дві принципово різні групи:
- Валідні — значення, які система має прийняти й обробити за основним сценарієм. Для віку це діапазон 18–65.
- Невалідні — значення, які система має відхилити: показати помилку, не пропустити далі, не зламатися. Для віку — числа поза діапазоном і все, що числом не є.
Новачки зазвичай зосереджуються на валідних класах: перевірив, що 30 приймається, — і поле «протестоване». На практиці дуже багато дефектів живе саме в обробці невалідного: забутий валідатор, крива помилка, 500-ка замість акуратного повідомлення, «abc» яке пролізло в базу. Валідний клас перевіряє, що фіча працює; невалідні — що вона не розвалюється від реального користувача, який вставить у поле що завгодно.
Зверни увагу: невалідних класів майже завжди більше, ніж валідних. Це нормально і навіть показово — якщо в твоєму розбитті один валідний клас і один невалідний «щось не те», розбиття, найімовірніше, неповне.
Класи входу і виходу
Досі ми ділили введення, але техніка працює з будь-якими даними. ISTQB прямо каже: розбивати можна вхідні й вихідні значення, внутрішні значення, часові параметри, конфігураційні елементи — усе, де є групи «обробляється однаково».
Найкорисніші на практиці після входів — класи виходу. Приклад: інтернет-магазин рахує знижку — 0% для замовлень до 1000 грн, 5% від 1000 до 5000, 10% понад 5000. На вході — сума замовлення, але подивись на вихід: система може видати рівно три різні знижки. Це три вихідні класи, і кожен вартий тесту. Щоб покрити вихідний клас, підбираєш вхід, який його породжує: замовлення на 500 грн → 0%, на 3000 → 5%, на 7000 → 10%.
Чому це окремий кут, а не той самий? Бо класи за входом і за виходом не завжди збігаються. Повідомлення про помилки — теж виходи: якщо для «занадто малого» і «занадто великого» віку система показує різні тексти, це два різні вихідні класи, і зліпити їх в один тест «поле показує помилку» означає не перевірити половину поведінки. У API-тестуванні той самий принцип: різні статус-коди відповіді — різні вихідні класи (докладніше — у розділі про API-тестування).
Кожен невалідний клас — окремим тестом
Правило, яке часто перевіряють на співбесідах: валідні значення можна комбінувати в одному тесті, а невалідні — ні, кожне йде окремим кейсом, поки решта полів валідні.
Причина називається маскуванням дефектів (fault masking). Уяви форму реєстрації з полями «вік» та «email». Ти вирішив зекономити і в одному тесті ввів вік «abc» і email без «@». Форма показала помилку про вік — тест «пройшов». Але валідацію email ти не перевірив: система могла зупинитися на першій помилці й до другої просто не дійти. Якщо валідатор email зламаний — ти про це не дізнаєшся, поки не дізнається користувач.
Тому кожен невалідний клас перевіряється в «тепличних» умовах: усі інші поля валідні, і якщо система лається — лається саме на те, що ти зіпсував. Один тест — одна причина впасти; той самий принцип атомарності, що й у структурі тест-кейсів.
Комбінування без вибуху кейсів
Поки поле одне — все просто. Але форма з кількох полів провокує перемножувати: кожен клас першого поля з кожним класом другого і третього. Порахуймо на формі замовлення:
- вік: 1 валідний клас + 3 невалідні (менше 18, більше 65, не число);
- email: 1 валідний + 2 невалідні (без «@», порожній);
- доставка: 2 валідні (курʼєр і самовивіз — різні гілки логіки) + 1 невалідний (не вибрано).
Повне перемноження: 4 × 3 × 3 = 36 кейсів на три нещасні поля. Додай четверте — і рахунок піде на сотні. Це і є комбінаторний вибух.
Вихід — покриття «кожен вибір» (each choice coverage): кожен клас має зʼявитися хоча б в одному тесті, але не в усіх можливих поєднаннях. Рецепт:
- Валідні класи пакуємо разом. Один тест може закрити по валідному класу з кожного поля одночасно. Тестів треба стільки, скільки валідних класів у «найбагатшого» поля — тут два (курʼєр і самовивіз).
- Невалідні — по одному за раз, решта полів валідні. Тестів — сума всіх невалідних класів: 3 + 2 + 1 = 6.
Разом 2 + 6 = 8 тестів замість 36:
| # | Вік | Доставка | Що закриває | |
|---|---|---|---|---|
| 1 | 30 | user@mail.com | курʼєр | усі валідні класи + гілка «курʼєр» |
| 2 | 42 | olia@mail.com | самовивіз | гілка «самовивіз» |
| 3 | 12 | user@mail.com | курʼєр | вік менше 18 |
| 4 | 80 | user@mail.com | курʼєр | вік більше 65 |
| 5 | abc | user@mail.com | курʼєр | вік не число |
| 6 | 30 | без «@» | курʼєр | невалідний email |
| 7 | 30 | порожній | курʼєр | порожній email |
| 8 | 30 | user@mail.com | не вибрано | доставка не вибрана |
Чесна межа техніки: each choice достатньо, коли поля незалежні — поведінка кожного не залежить від значень сусідів. Якщо логіка спрацьовує саме на поєднаннях («знижка діє лише для курʼєра І суми понад 1000»), потрібні таблиці рішень (decision tables), а для великих конфігураційних матриць — попарне тестування (pairwise testing).
Класи як параметризований автотест
Потребує: JS/TS. Блок можна пропустити при першому проході — суть глави від цього не постраждає.
Класи еквівалентності природно лягають на параметризовані тести: техніка дає таблицю «значення → очікування», а тест лишається один — цикл по рядках таблиці. У Playwright це виглядає так:
import { test, expect } from '@playwright/test';
// Кожен рядок — окремий невалідний клас зі своїм очікуваним повідомленням
const invalidAges = [
{ value: '17', error: 'Мінімальний вік — 18 років', title: 'нижче мінімуму' },
{ value: '66', error: 'Максимальний вік — 65 років', title: 'вище максимуму' },
{ value: 'abc', error: 'Вік має бути числом', title: 'не число' },
{ value: '', error: 'Поле обовʼязкове', title: 'порожнє значення' },
];
for (const { value, error, title } of invalidAges) {
test(`реєстрація відхиляє вік: ${title}`, async ({ page }) => {
await page.goto('/registration');
await page.getByLabel('Вік').fill(value);
await page.getByLabel('Email').fill('user@mail.com'); // решта полів — валідні
await page.getByRole('button', { name: 'Зареєструватися' }).click();
await expect(page.getByText(error)).toBeVisible();
});
}
Зверни увагу на три речі. По-перше, цикл автоматично виконує правило «кожен невалідний клас — окремим тестом»: кожен рядок стає незалежним тестом зі своєю назвою у звіті. По-друге, очікувана помилка лежить у даних поруч зі значенням — різні повідомлення для різних класів документують, що це справді різні класи, а не «якась помилка десь зʼявилась». По-третє, додати новий клас (скажімо, відʼємне число) — це один рядок у масиві, а не копіпаст тесту.
Так тест-дизайн перестає бути «паперовою» дисципліною: таблиця класів із аналізу вимог без змін стає тестовими даними автотесту.
Типові помилки
- Кілька значень з одного класу. Тести з віком 25, 30 і 40 виглядають як ретельність, а насправді це один клас: три тести, які завжди падатимуть і проходитимуть разом. Два з них — баласт, який доведеться підтримувати.
- «Невалідний ввід» одним класом. Виглядає як один акуратний негативний тест, а насправді порожнє поле, букви і число поза діапазоном проходять різними валідаторами й дають різні повідомлення — це різні класи, і зламатися кожен може окремо.
- Розбиття без «сміттєвих» класів. Три класи «менше / діапазон / більше» виглядають як повне покриття, а насправді нечислові введення нікуди не впали — правило «кожне значення має свій клас» порушено. Перевірка повноти проста: назви будь-яке дике введення і спитай себе, в який клас воно потрапляє.
- Кілька невалідних значень в одному тесті. Виглядає як економія кейсів, а насправді маскування дефектів: система зупиняється на першій помилці, і решта валідаторів лишаються неперевіреними.
- Представник із середини як єдина перевірка класу. Клас виглядає однорідним за специфікацією, а насправді реалізація може «відрізати» його межі неправильно — класика off-by-one. Тому представник із середини доповнюють перевірками країв — це вже граничні значення.
Підсумок
- Клас еквівалентності — множина значень, які система за специфікацією обробляє однаково; з класу достатньо одного представника.
- Класи не перетинаються і разом покривають усі можливі введення — включно з порожніми, нечисловими та «сміттєвими».
- Розбивати можна не лише входи: виходи (знижки, статуси, тексти помилок) — теж класи, і вони не завжди збігаються з вхідними.
- Валідні класи комбінуються в одному тесті; кожен невалідний перевіряється окремо на тлі валідних сусідів — інакше помилки маскують одна одну.
- Мінімум тестів для форми: максимум валідних класів серед полів + сума всіх невалідних. Гіпотезу «всередині класу все однаково» страхують граничні значення.
Що питають на співбесіді
- «Поле приймає числа від 1 до 100. Скільки тестів потрібно і чому?» Інтервʼюер дивиться, чи назвеш ти невалідні класи (менше 1, більше 100, не число, порожньо), а не лише «перевірю 50». І чи поясниш, чому 50 і 70 — це один тест, а не два. Повна сильна відповідь включає й межі — це вже звʼязка з граничними значеннями.
- «Чим класи еквівалентності відрізняються від граничних значень?» Очікувана суть: класи відповідають на питання «які групи значень існують», межі — «де саме групи стикаються»; техніки застосовують разом, а не замість.
- «Чому не можна перевірити два невалідні поля одним тестом?» Ключове слово — маскування: система зупиняється на першій помилці, друга валідація лишається неперевіреною.
- «Форма з пʼяти полів — як не отримати сотні кейсів?» Тут чекають на each choice: валідні пакуємо, невалідні по одному. Якщо згадаєш, що для залежних полів це не працює і потрібна таблиця рішень, — плюс до senior-балів.
- «Протестуйте поле вводу». Класика, в якій класи еквівалентності — скелет відповіді: спершу уточнити вимоги, потім валідні й невалідні класи, потім межі. Повний розбір таких задач — у главі про класичні задачі співбесід.
Загалом інтервʼюер оцінює не заучене означення, а системність: чи є в твоїй відповіді словник (валідний/невалідний клас, представник, покриття), чи вмієш ти порахувати й обґрунтувати кількість тестів, і чи бачиш межі техніки.
Джерела
- ISTQB CTFL Syllabus v4.0 — розділ 4.2.1 «Equivalence Partitioning»: означення, валідні/невалідні класи, покриття та each choice coverage. Правило «кожен невалідний клас — окремо» явно сформульоване в попередній версії силабуса (CTFL 2018 v3.1, розділ 4.2.1).
- ISTQB Glossary — канонічні означення термінів equivalence partitioning та coverage item.
- Playwright: Parameterize tests — офіційна документація патерну «цикл по тестових даних» із прикладу вище.
Що таке клас еквівалентності і яку проблему розвʼязує ця техніка?
Клас еквівалентності — множина значень, які система за специфікацією обробляє однаково, тому з класу достатньо перевірити одного представника. Техніка відповідає на проблему неможливості вичерпного тестування: поле «вік» з діапазоном 18–65 приймає безліч введень — цілі числа, дроби, відʼємні, букви, порожній рядок — і перебрати все нереально, а тицьнути навмання страшно, бо незрозуміло, що саме перевірив і що пропустив. Розбиття на класи (equivalence partitioning) перетворює цей хаос на короткий список тестів: замість тисяч значень — по одному на клас. Практичний наслідок — кількість тестів стає аргументованою: можеш пояснити, чому саме ці значення і чому їх достатньо.
Яким вимогам має відповідати коректне розбиття на класи?
Двом. Перша — класи не перетинаються: кожне значення належить рівно одному класу; якщо 65 потрапляє і у «валідний діапазон», і в «занадто великі», межі визначені неохайно. Друга — класи разом покривають усі можливі введення: не лише числа, а й рядок «abc», порожнє поле і пробіли мають кудись впасти. На практиці частіше порушують другу вимогу: розбиття «менше / діапазон / більше» виглядає акуратно, але нечислові введення лишаються без класу. Швидка перевірка повноти — назвати будь-яке дике введення і спитати себе, в який клас воно потрапляє; якщо ні в який — розбиття неповне.
Чому з кожного класу достатньо одного значення?
Бо в основі техніки лежить гіпотеза еквівалентності: якщо система за специфікацією обробляє всі значення класу однаково, то тест із представником або пройшов би для всього класу, або впав би для всього класу. Саме ця гіпотеза дає економію — не треба перевіряти всі 48 валідних значень віку, достатньо одного, наприклад 30. Важливо памʼятати: це гіпотеза за специфікацією, а не факт про код — реалізація може ділити діапазон інакше, ніж написано у вимогах, і розбіжності найчастіше живуть на межах класів. Тому представника з середини доповнюють перевірками країв — це вже аналіз граничних значень, природний напарник техніки.
Що таке валідні й невалідні класи і чому невалідним варто приділяти більше уваги?
Валідні класи — значення, які система має прийняти й обробити за основним сценарієм; невалідні — значення, які вона має відхилити: показати помилку, не пропустити далі, не зламатися. Новачки зосереджуються на валідних: перевірив, що 30 приймається, — і поле «протестоване». Але дуже багато дефектів живе саме в обробці невалідного: забутий валідатор, крива помилка, 500-ка замість акуратного повідомлення, «abc», що пролізло в базу. Валідний клас перевіряє, що фіча працює; невалідні — що вона не розвалюється від реального користувача, який вставить у поле що завгодно. Показовий орієнтир: невалідних класів майже завжди більше, ніж валідних, і якщо в розбитті один валідний клас і один невалідний «щось не те» — воно, найімовірніше, неповне.
Поле приймає числа від 18 до 65. Скільки тестів потрібно і чому?
Мінімум чотири — по одному на клас: валідний діапазон 18–65 (представник 30), число менше 18 (представник 10), число більше 65 (представник 80) і нечислові введення — «abc», порожньо, пробіли. Ключова частина відповіді — пояснити, чому не більше: 30 і 40 належать одному валідному класу, тож другий тест не додає покриття, а лише баласт. І чому не менше: кожен невалідний клас проходить власним валідатором і може зламатися окремо, тому зліпити їх у «щось невалідне» означає не перевірити частину поведінки. Сильна відповідь додає й межі — 17/18 і 65/66: це вже звʼязка з граничними значеннями, які страхують гіпотезу еквівалентності на краях класів.
Як порахувати покриття технікою класів еквівалентності?
Прозоро: кількість класів, закритих тестами, поділити на загальну кількість класів. Чотири класи в поля «вік» — чотири тести дають повне покриття технікою; два тести — половину, і одразу видно, які саме класи лишилися дірками. Саме ця прозорість — головна перевага перед «перевірив кілька значень»: цифру можна аргументувати й захистити перед командою чи інтервʼюером. Варто лише памʼятати, що повне покриття за класами не означає «все протестовано» — це покриття однією технікою, яку доповнюють граничними значеннями та іншими техніками.
Чому не можна перевірити два невалідні поля одним тестом?
Через маскування дефектів (fault masking): система часто зупиняється на першій помилці й до другої просто не доходить. Класичний приклад — форма з віком «abc» та email без «@» в одному тесті: форма показала помилку про вік, тест «пройшов», але валідацію email ніхто не перевірив — якщо вона зламана, дізнається про це користувач. Тому кожен невалідний клас перевіряють у «тепличних» умовах: усі інші поля валідні, і якщо система лається — лається саме на те, що ти зіпсував. Це той самий принцип атомарності, що й у структурі тест-кейсів: один тест — одна причина впасти. Валідні класи, навпаки, комбінувати можна — вони не заважають одне одному.
Що таке класи виходу і навіщо вони, якщо вже є класи входу?
Розбивати можна будь-які дані, де є групи «обробляється однаково»: ISTQB прямо називає вхідні й вихідні значення, внутрішні значення, часові параметри та конфігураційні елементи. Класи виходу — найкорисніші на практиці після входів. Приклад: магазин дає 0% знижки до 1000 грн, 5% від 1000 до 5000 і 10% понад 5000 — система може видати рівно три різні знижки, це три вихідні класи, кожен вартий тесту. Щоб покрити вихідний клас, підбираєш вхід, який його породжує: 500 грн дає 0%, 3000 — 5%, 7000 — 10%. Окремий кут потрібен, бо класи за входом і виходом не завжди збігаються — дивлячись лише на вхід, легко пропустити цілу гілку поведінки на виході.
Система показує різні повідомлення для «занадто малого» і «занадто великого» віку. Це один тест чи два?
Два, бо повідомлення про помилки — теж виходи, а різні тексти — різні вихідні класи. Тест «поле показує помилку» зліплює їх в один і фактично не перевіряє половину поведінки: повідомлення для «більше 65» може бути кривим, відсутнім чи від іншого поля — і такий тест цього не помітить. Той самий принцип працює в API-тестуванні: різні статус-коди відповіді — різні вихідні класи, і кожен вартий окремої перевірки. Практичний бонус: очікуваний текст помилки поруч зі значенням у тестових даних документує, що це справді різні класи, а не «якась помилка десь зʼявилась».
Форма з пʼяти полів — як не отримати сотні тест-кейсів?
Не перемножувати класи всіх полів між собою, а застосувати покриття «кожен вибір» (each choice coverage): кожен клас має зʼявитися хоча б в одному тесті, але не в усіх можливих поєднаннях. Рецепт із двох кроків. Валідні класи пакуємо разом: один тест закриває по валідному класу з кожного поля одночасно, тож валідних тестів треба стільки, скільки валідних класів у «найбагатшого» поля. Невалідні — по одному за раз на тлі валідних сусідів, тож невалідних тестів — сума всіх невалідних класів. Для форми з віком (1 валідний + 3 невалідні), email (1 + 2) і доставкою (2 + 1) це 2 + 6 = 8 тестів замість повного перемноження 4 × 3 × 3 = 36.
Коли each choice coverage не спрацює і що робити натомість?
Each choice достатньо, лише коли поля незалежні — поведінка кожного не залежить від значень сусідів. Якщо логіка спрацьовує саме на поєднаннях («знижка діє лише для курʼєра І суми понад 1000»), набір «кожен клас хоч раз» може жодного разу не зібрати критичну комбінацію — і дефект пройде повз. Для залежних полів потрібні таблиці рішень, які явно перелічують комбінації умов, а для великих конфігураційних матриць — попарне тестування (pairwise). Уміння назвати цю межу — те, що відрізняє senior-відповідь від завченого рецепта.
Чим класи еквівалентності відрізняються від граничних значень і чому їх застосовують разом?
Класи відповідають на питання «які групи значень існують», граничні значення — «де саме ці групи стикаються». Класи дають скелет: валідний діапазон, невалідні сусіди, сміттєві введення; межі перевіряють краї кожного класу — 17/18, 65/66. Разом техніки страхують головну слабкість одна одної: гіпотеза «всередині класу все однаково» найчастіше ламається саме на межах — класика off-by-one, коли реалізація «відрізає» діапазон не там, де каже специфікація. А межі без класів не дають системної картини, що взагалі треба перевіряти. Тому це не «або-або»: техніки застосовують разом, а не замість.
Колега написав три тести для поля «вік 18–65»: значення 25, 30 і 40. Що з цим не так?
Усі три значення — представники одного валідного класу, тож це фактично один тест, скопійований тричі: вони завжди падатимуть і проходитимуть разом. Два з трьох — баласт, який не додає покриття, але який доведеться підтримувати: повільніший прогін, більше коду, більше правок при зміні вимог. Виглядає як ретельність, а справжня ретельність — закрити інші класи: менше 18, більше 65, не число, порожнє поле. Правильна пропозиція на ревʼю: лишити одне значення з середини діапазону, а звільнений бюджет витратити на невалідні класи й межі.
Як класи еквівалентності перекладаються в автотести?
Через параметризацію: техніка дає таблицю «значення → очікування», а тест лишається один — цикл по рядках таблиці. У Playwright це масив обʼєктів «значення + очікувана помилка + назва класу» і цикл навколо test(): кожен рядок стає незалежним тестом зі своєю назвою у звіті. Цикл автоматично виконує правило «кожен невалідний клас — окремим тестом», а очікувана помилка в даних поруч зі значенням документує, що класи справді різні, а не «якась помилка десь зʼявилась». Додати новий клас — один рядок у масиві, а не копіпаст тесту. Так тест-дизайн перестає бути «паперовою» дисципліною: таблиця класів з аналізу вимог без змін стає тестовими даними автотесту.
На співбесіді просять: «Протестуйте поле вводу». Як будувати відповідь?
Почати з уточнення вимог, бо без специфікації немає класів: який тип даних, який діапазон, чи обовʼязкове поле. Далі — скелет на класах еквівалентності: валідні класи, потім невалідні — поза діапазоном з обох боків, не той тип, порожньо, пробіли — і для кожного представник. Потім краї кожного класу — звʼязка з граничними значеннями. Інтервʼюер оцінює не заучене означення, а системність: чи є у відповіді словник (валідний/невалідний клас, представник, покриття), чи вмієш порахувати й обґрунтувати кількість тестів, і чи бачиш межі техніки.
Три кейси, де класи еквівалентності — робочий інструмент, а не теорія: ревʼю чужих тест-кейсів на поле «вік» (де баласт, де дірки, де маскування), підрахунок мінімуму кейсів для форми реєстрації без комбінаторного вибуху та вихідні класи знижки як параметризований API-тест.
Кейс 1. Ревʼю тест-кейсів: баласт, дірки й маскування
Вимога: поле «вік» на формі реєстрації приймає цілі числа від 18 до 65. Колега приніс на ревʼю пʼять кейсів. Розкладемо кожен по класах:
| # | Тест | Який клас закриває | Вердикт |
|---|---|---|---|
| 1 | вік 25, решта полів валідні | валідний 18–65 | лишаємо — представник валідного класу |
| 2 | вік 30, решта валідні | той самий валідний клас | баласт: падатиме й проходитиме разом із №1 |
| 3 | вік 40, решта валідні | той самий валідний клас | баласт із тієї ж причини |
| 4 | вік 10, решта валідні | невалідний «менше 18» | лишаємо |
| 5 | вік «abc» та email без «@» одночасно | два невалідні класи одразу | переписати: маскування дефектів |
Що дивитися і чому:
- Кейси 2–3 не додають покриття. 25, 30 і 40 — один клас, тож за гіпотезою еквівалентності це той самий тест тричі. Два з них — код, який доведеться підтримувати, не отримуючи нічого взамін.
- Кейс 5 перевіряє менше, ніж обіцяє. Форма, найімовірніше, зупиниться на першій помилці — про вік — і валідація email лишиться неторканою. Правильно: вік «abc» з валідним email — один кейс, невалідний email з валідним віком — інший.
- Дірки шукаються питанням «куди падає дике введення?» У наборі немає класів «більше 65» і «не число / порожньо / пробіли» — а це рівно ті введення, які реальний користувач принесе першим же днем.
- Підсумкова арифметика. Чотири класи в поля «вік» — мінімум чотири тести на нього, плюс окремі кейси на невалідні класи email. Після ревʼю набір стає меншим за обсягом валідних перевірок і повнішим за покриттям.
Кейс 2. Форма реєстрації: мінімум кейсів без вибуху
Форма з трьох полів:
- пароль: 1 валідний клас (8–20 символів) + 3 невалідні (коротший за 8, довший за 20, порожній);
- email: 1 валідний + 2 невалідні (без «@», порожній);
- роль: 2 валідні («покупець» і «продавець» — різні гілки логіки) + 1 невалідний (не вибрано).
Повне перемноження дало б 4 × 3 × 3 = 36 кейсів. Покриття «кожен вибір» (each choice): валідні пакуємо разом — тестів стільки, скільки валідних класів у «найбагатшого» поля (роль, два); невалідні по одному на тлі валідних сусідів — 3 + 2 + 1 = 6. Разом 8:
| # | Пароль | Роль | Що закриває | |
|---|---|---|---|---|
| 1 | Qwerty12 | user@mail.com | покупець | усі валідні класи + гілка «покупець» |
| 2 | Zxcvbn34 | olia@mail.com | продавець | гілка «продавець» |
| 3 | Qw1 | user@mail.com | покупець | пароль коротший за 8 |
| 4 | Qwerty12Qwerty12Qwert | user@mail.com | покупець | пароль довший за 20 |
| 5 | порожній | user@mail.com | покупець | порожній пароль |
| 6 | Qwerty12 | без «@» | покупець | невалідний email |
| 7 | Qwerty12 | порожній | покупець | порожній email |
| 8 | Qwerty12 | user@mail.com | не вибрано | роль не вибрана |
Що дивитися і чому:
- Кожен невалідний тест псує рівно одне поле. Якщо форма лається — лається саме на зіпсоване, і жодна валідація не ховається за сусідньою помилкою.
- Два валідні тести, а не один — бо «покупець» і «продавець» ведуть різними гілками логіки, це два валідні класи одного поля, і кожен має зʼявитися хоч раз.
- Формула мінімуму захищається на ревʼю: максимум валідних класів серед полів (2) + сума невалідних (6). Не «я написав вісім кейсів», а «вісім — це мінімум для повного покриття класів, і ось чому».
- Слідкуй за незалежністю полів. Якщо зʼявиться правило «продавець зобовʼязаний вказати компанію», поведінка поля залежить від ролі — each choice уже не досить, потрібна таблиця рішень.
Кейс 3. Вихідні класи знижки як параметризований API-тест
Вимога: знижка 0% для замовлень до 1000 грн, 5% від 1000 до 5000, 10% понад 5000. На вході — будь-яка сума, але вихідних класів рівно три, і кожен вартий тесту. Під кожен вихід підбираємо вхід, який його породжує:
import { test, expect } from '@playwright/test';
// Кожен рядок — вихідний клас знижки та вхід, який його породжує
const discountClasses = [
{ total: 500, discount: 0, title: 'без знижки' },
{ total: 3000, discount: 5, title: 'середня знижка' },
{ total: 7000, discount: 10, title: 'максимальна знижка' },
];
for (const { total, discount, title } of discountClasses) {
test(`знижка для суми ${total}: ${title}`, async ({ request }) => {
const res = await request.post('/api/cart/discount', { data: { total } });
expect(res.status()).toBe(200);
const body = await res.json();
expect(body.discountPercent).toBe(discount);
});
}
Що дивитися і чому:
- Вхідний кут ховає гілки. Дивлячись лише на вхід («сума — число»), легко здати один тест «знижка рахується». Погляд на вихід показує: система вміє видати рівно три різні знижки, і кожна може зламатися окремо.
- Асерт — на конкретне значення виходу, а не «знижка є».
expect(body.discountPercent).toBe(5)ловить переплутані пороги; перевірка «поле не порожнє» — ні. - Статус-коди — теж вихідні класи. Якщо контракт API описує відмову для нечислової суми — це окремий вихідний клас і окремий негативний тест, а не додаток до цих трьох.
- Межі 1000 і 5000 — наступний крок. До якого класу належить рівно 1000, вирішує специфікація («від 1000» — отже, 5%), і саме тести на краях ловлять off-by-one — це вже техніка граничних значень, напарник класів.
Ідея розбиття
- Можу пояснити, чому вичерпне тестування неможливе і як розбиття на класи еквівалентності (equivalence partitioning) перетворює нескінченні введення на скінченний список тестів.
- Знаю означення: клас еквівалентності — множина значень, які система за специфікацією обробляє однаково, тож достатньо одного представника.
- Розумію гіпотезу еквівалентності: представник пройшов — пройшов би весь клас, впав — упав би весь клас; і памʼятаю, що це гіпотеза за специфікацією, а не факт про код.
- Знаю два правила коректного розбиття: класи не перетинаються, і кожне можливе введення має свій клас — включно з «abc», порожнім полем і пробілами.
- Можу порахувати покриття технікою: закриті тестами класи поділити на всі класи — і захистити цю цифру.
Валідні й невалідні класи
- Знаю різницю: валідний клас система приймає за основним сценарієм, невалідний — має відхилити з акуратною помилкою, не зламавшись.
- Розумію, чому невалідних класів майже завжди більше і чому «один валідний + один невалідний» — ознака неповного розбиття.
- Памʼятаю, що дефекти часто живуть в обробці невалідного: забутий валідатор, 500-ка замість повідомлення, сміття, що пролізло в базу.
Класи входу і виходу
- Знаю, що розбивати можна не лише входи: виходи, внутрішні значення, часові параметри, конфігураційні елементи (за ISTQB).
- Можу навести приклад вихідних класів (знижка 0% / 5% / 10%) і пояснити, як підбирати вхід, що породжує кожен вихід.
- Розумію, чому різні тексти помилок і різні статус-коди API — різні вихідні класи, і чому вхідні й вихідні класи не завжди збігаються.
Комбінування полів
- Знаю правило: валідні класи можна пакувати в один тест, кожен невалідний — окремо, поки решта полів валідні.
- Можу пояснити маскування дефектів (fault masking): система зупиняється на першій помилці, друга валідація лишається неперевіреною.
- Знаю each choice coverage і формулу мінімуму для форми: максимум валідних класів серед полів плюс сума всіх невалідних.
- Памʼятаю межу техніки: each choice працює лише для незалежних полів; для залежних потрібні таблиці рішень, для великих конфігураційних матриць — попарне тестування (pairwise).
Автотести й типові пастки
- Можу перекласти таблицю класів у параметризований тест: масив «значення → очікування» плюс цикл, кожен рядок — окремий тест у звіті.
- Розпізнаю баласт: кілька значень з одного класу (25, 30, 40) — це один тест, скопійований тричі.
- Не зліплюю «невалідний ввід» в один клас: порожнє поле, букви й число поза діапазоном проходять різними валідаторами й ламаються окремо.
- Памʼятаю, що представника з середини класу доповнюють перевірками країв — інакше off-by-one на межі лишиться непоміченим.
Що таке клас еквівалентності?
Питання
Клас еквівалентності — що це?