В требовании одна строка: «Возраст — от 18 до 60». На форме проверять нечего и одновременно всё: 17, 0, −5, 150, «двадцать пять», пустое поле, вставленный абзац. Одних целых чисел от 0 до 150 — 151 проверка на поле, полей восемь.
Тест-дизайн отвечает на вопрос «что именно взять». Классов эквивалентности и граничных значений хватает на большую часть полей; где проверять надо не поле, а порядок действий, добавляется третий приём — переходы состояний. Разберём все три на заказе.
Классы отвечают на вопрос «какие ситуации вообще есть» и убирают повторы: 15, 16 и 17 для программы одно и то же. Границы отвечают на вопрос «где ситуация меняется» — и именно там стоит сравнение, в котором ошибаются. 15 и 70 из списка уходят: их работу делают 17 и 61, которые лежат в тех же классах, но ещё и упираются в край.
Классы эквивалентности: сорок три проверки превращаются в три
Кажется, что 19 и 37 — разные данные. Для формы разные, для кода — нет: за полем стоит одно условие «не меньше 18 и не больше 60 — принимаем, иначе ошибка», и 19 с 37 идут по одной его ветке: те же строки кода, та же запись в базу, тот же ответ. Двадцать значений одной ветки — одна проверка, повторённая двадцать раз.
Так и устроен класс эквивалентности: группа значений, на которые программа реагирует одинаково. Отличается реакция хоть чем-нибудь — на 0 «введите возраст», на −5 «возраст не может быть отрицательным» — это уже два класса.
У «возраста от 18 до 60» групп три: меньше 18, от 18 до 60, больше 60. У соседнего поля суммы с порогами 3000 ₽ и 300 000 ₽ классов четыре, оба средних проходят, но по-разному.
Отдельно живут классы, которых в требовании нет: пустое поле, буквы вместо цифр, отрицательное число, текст на пять тысяч символов. Их называют невалидными, а проверки по ним — негативными: требование писали не про того, кто вставит строку с пробелом на конце.
Границы: почему ошибки собираются на краю класса
Значения внутри класса равноценны, и логично взять из середины что-нибудь круглое — 30, 1500. Так и пропускают большую часть багов: середина класса — самое спокойное место в программе, решение принимается на краю.
Границу пишет человек, одним сравнением, а у сравнения есть близнец в один символ: «больше» и «больше или равно». Перепутали — поле принимает 17 или не принимает 60. Это ошибка на единицу, и проверка на 30 её не увидит. Вторая причина — само требование: «доставка бесплатная от 3000 ₽» — ровно 3000 уже бесплатно? «От», «до» и «свыше» двусмысленны, и аналитик с разработчиком читают их по-разному.
Вдобавок переход через край меняет не только ответ: появляется строка «доставка 0 ₽», кнопка «Оплатить» сменяется сообщением про менеджера. Смотрят и на то, что изменилось рядом.
Минус один, ровно, плюс один — и что считать шагом
Правило: на каждую границу берём пару — граничное значение и соседнее с ним по ту сторону. Границ у «от 18 до 60» две, пар тоже две: 17 и 18, 60 и 61. Односторонняя проверка ловит половину ошибок: убедились, что 18 принимается, и ушли, а «принимает 17» осталось неувиденным. Третью точку (17, 18, 19) добавляют, когда граница вычисляется: возраст из даты рождения, лимит из настроек. У целых чисел шаг — единица, дальше зависит от того, чем заполняют поле.
Деньги. Шаг — копейка: не 2999 и 3000, а 2999.99 и 3000.00. На целых рублях не видно ошибок округления: 2999.995 округлится до 3000 и получит бесплатную доставку. Заодно видно, что поле делает с запятой вместо точки.
Даты. Шаг — день, а если в требовании есть время — секунда: «акция до 31 августа включительно» действует 31 августа в 23:59:59 и не действует 1 сентября в 00:00:00. С часовыми поясами границу ловят, когда у покупателя уже завтра, а на сервере ещё сегодня.
Длина текста. Шаг — символ, границ две: снизу пусто и один символ, сверху ровно предел и предел плюс один. Сам «символ» спорен: «ё», эмодзи и иероглиф считаются по-разному, и поле «до 100 символов» иногда обрывает текст на девяностом.
Взяли не тот шаг — проверили не границу, а два случайных значения рядом. Своя единица есть у списка (элементы) и у файла (байты), а у флага и трёх пунктов в списке границ нет — там два-три случая.
Шесть проверок вместо сорока трёх — строками чек-листа
Сначала классами размечаем поле, включая невалидные. Потом на каждую границу берём пару с двух сторон: 17, 18, 60, 61. И добавляем представителя туда, куда границы не дотянулись: 30, «abc», пусто.
| Что вводим | Что ждём | Почему |
|---|---|---|
| 17 | ошибка «возраст от 18 лет» | минус один к нижней границе |
| 18 | форма отправляется | нижняя граница, ровно |
| 30 | форма отправляется | представитель класса |
| 60 | форма отправляется | верхняя граница, ровно |
| 61 | ошибка «возраст до 60 лет» | плюс один к верхней границе |
| пусто | ошибка «заполните поле» | класс «нет значения» |
| abc | буквы не вводятся | класс «не число» |
Слово «ошибка» в средней колонке результатом не является: так не отличить правильное поведение от «форма молча не отправилась» — пишите текст сообщения и место, где оно появляется, из этой строки потом собирается баг-репорт. Третья колонка кажется лишней до дня, когда нижнюю границу поднимут с 18 до 21: сразу видно, что переписать.
Раскладывая поле на классы, вы упрётесь в место, где требование молчит, — та же бесплатная доставка ровно на 3000. Не угадывайте: это находка для вопросов к требованиям. А когда результат зависит от сочетания условий — три условия по три варианта дают 27 сочетаний, — нужны таблицы решений и попарное тестирование.
Переходы состояний: проверяем не значение, а порядок
Классы и границы разбирают поле в один момент. Но у заказа нет «правильного значения» — у него есть жизнь. Оплата работает, «Отменить» работает, а баг сидит в порядке: отменяется заказ, который час назад уехал с курьером.
Состояние — это то, что с заказом уже случилось и что ему теперь можно. Основной путь: новый → оплачен → собран → отправлен → доставлен; рядом два боковых — отменён, пока заказ не уехал, и возврат, когда он доставлен.
| Состояние | Что разрешено | Что запрещено |
|---|---|---|
| новый | оплатить, отменить | собрать, отправить |
| оплачен | собрать, отменить с возвратом | оплатить второй раз |
| собран | отправить, отменить | оплатить |
| отправлен | доставить | отменить, оплатить |
| доставлен | оформить возврат | отменить, доставить |
| отменён | ничего | всё остальное |
Каждая клетка второй колонки — позитивная проверка: довести заказ до состояния, выполнить действие, убедиться, что состояние сменилось, а деньги списались и товар зарезервирован. Клетка третьей — негативная: то же начало, но система обязана отказать внятно, а состояние — остаться прежним.
Баги собираются в третьей колонке: запрет чаще нарисован на экране, чем записан в коде. Кнопку «Отменить» после отправки прячут, но в соседней вкладке остался старый экран, а запрос повторяется и напрямую. Та же история с повторным переходом: двойной клик по «Оплатить» списывает деньги дважды. Сменилось ли состояние, видно в базе — в поле статуса.
Коротко
- Класс эквивалентности — значения с одинаковой реакцией программы; отличается реакция хоть чем-нибудь — это другой класс, и классов всегда больше, чем строк в требовании.
- Границы — самое багоопасное место: там написанное руками сравнение и двусмысленные «от», «до», «свыше».
- На каждую границу берут пару с двух сторон — 17 и 18, 60 и 61: односторонняя проверка ловит половину ошибок.
- Шаг зависит от типа поля: у денег копейка, у дат день или секунда, у текста символ.
- Поле «возраст от 18 до 60» закрывается шестью-семью проверками вместо сорока трёх.
- У объекта с жизненным циклом проверяют переходы: разрешённый — позитивная проверка, запрещённый — негативная. Баги живут на запрещённых: отменить отправленный, оплатить дважды.
Что почитать дальше
- Таблицы решений и попарное тестирование — когда результат зависит от сочетания условий.
- Чек-листы и тестовые данные — где живут получившиеся строки.
- Как писать тест-кейс: шаги и ожидаемый результат — как «17 → ошибка» становится проверкой для другого.
- Ревью требований: техники и хорошие вопросы — что спросить, когда про границу не написано.