Блог о тестировании ·

Граничные значения и классы эквивалентности: как проверять на практике

Проверить все возможные значения поля нельзя: их слишком много. Две техники решают эту задачу вместе — классы эквивалентности сокращают перебор, анализ границ говорит, какие значения из оставшихся проверять в первую очередь.

Классы эквивалентности: почему хватает одного значения

Поле «количество товара» принимает числа от 1 до 99. Проверять по очереди 1, 2, 3 и дальше бессмысленно: сервис обрабатывает их одинаково. Все значения, с которыми программа поступает одинаково, образуют класс — и одного представителя класса достаточно.

Классов обычно три: допустимые значения, значения ниже допустимого и выше. К ним добавляются отдельные классы для другого типа данных — пустое поле, строка вместо числа, дробное вместо целого, отсутствие поля вообще. Последний часто забывают, а ведёт он себя иначе, чем пустое значение.

Граница: место, где ломается чаще всего

Ошибки редко живут в середине диапазона. Они живут на краю, потому что там программист выбирал между «больше» и «больше либо равно» — и мог ошибиться на единицу. Эту ошибку так и называют: ошибка на единицу.

ISTQB различает двухточечный и трёхточечный анализ. В двухточечном для каждой границы берут значение самой границы и ближайшего соседнего класса: 0 и 1, затем 99 и 100. Трёхточечный добавляет ближайшее значение внутри допустимого класса: 2 и 98. Он дороже, но обнаруживает больше вариантов ошибки на единицу.

Поле «количество», допустимо 1–99
  • 0 ниже границы ожидаем отказ
  • 1 сама граница ожидаем успех
  • 2 внутри диапазона ожидаем успех
  • 98 внутри диапазона ожидаем успех
  • 99 сама граница ожидаем успех
  • 100 выше границы ожидаем отказ

Где границы прячутся в API

Число — самый заметный случай, но далеко не единственный. Границы есть почти у всего, что имеет размер, длину или срок.

  • Размер страницы: per_page=0, 1, максимум, максимум + 1. Частая находка — параметр молча игнорируется, и сервис отдаёт всё подряд.
  • Длина текста: пустая строка, один символ, максимум по требованиям, максимум + 1. Отдельно — только пробелы: формально строка не пустая.
  • Даты периода: начало равно концу, начало позже конца, период длиной в день, будущая дата.
  • Деньги: ноль, минимальная сумма, копейка сверх остатка, дробная часть длиннее двух знаков.
  • Частота действий: последний разрешённый запрос в окне и следующий за ним — тот, что должен получить отказ.

Как выглядит проверка границы

Смысл в том, чтобы менять ровно одно значение и сравнивать ответы. Если на границе и за границей сервис отвечает одинаково — проверки нет, и это дефект независимо от того, как он выглядит на экране.

Три запроса подряд, меняется одна цифра
GET /products?per_page=50   → 200, в data 50 позиций
GET /products?per_page=51   → ожидаем 422
GET /products?per_page=0    → ожидаем 422

Ожидаемый результат для каждой строки берётся из требований, а не из наблюдения. Если в требованиях максимум не задан — это само по себе находка: без верхней границы один запрос может попросить весь каталог разом.

Чего эти техники не ловят

Границы и классы работают с одним полем за раз. Дефекты, которые возникают от сочетания полей — скидка плюс количество плюс остаток на складе, — так не найти: для них нужны другие техники и просто внимание к тому, как значения влияют друг на друга.

Ещё они ничего не говорят о правах доступа и о том, что происходит при повторе одного и того же запроса. Это отдельные виды проверок, и начинать стоит с границ только потому, что они дают больше всего находок за меньшее время.

Как получить набор проверок из требования

Записывай классы до конкретных значений. Для поля “скидка — целое число от 0 до 30 включительно” классы такие: допустимое целое, меньше 0, больше 30, дробное, строка, null, поле отсутствует. Границы применимы только к упорядоченным классам; строка и null — отдельные классы, но не числовые соседи диапазона.

Минимальный осмысленный набор
-1 → ниже диапазона, отказ\n0  → нижняя граница, успех\n1  → внутри, успех (только 3-value BVA)\n29 → внутри, успех (только 3-value BVA)\n30 → верхняя граница, успех\n31 → выше диапазона, отказ\n2.5, "10", null, missing → отдельные классы

Один представитель класса достаточен только при обоснованном предположении, что обработка действительно одинакова. Если ноль имеет бизнес-смысл, отрицательные значения проходят другой валидатор, а 30 включает отдельное правило промоакции, это уже разные классы.

Где техника ломается и чем её дополнить

Эквивалентность — модель, а не свойство, которое система обязана соблюдать. Пересматривай классы после каждой находки: дефект часто показывает, что внутри предполагаемого класса есть скрытая граница.

  • Сочетания полей: используй decision table или pairwise, если результат зависит от нескольких условий.
  • Переходы статусов: используй state transition testing, а не набор независимых значений.
  • Права и владение объектом: строй матрицу actor × action × object.
  • Время и даты: уточняй timezone, включительность границы и переход летнего времени.
  • Деньги: уточняй единицу хранения, округление, валюту и допустимую точность до выбора чисел.

Разбор: суточный лимит перевода 10 000

Одно поле amount не описывает весь риск. Лимит относится к накопленной сумме за день, поэтому тест зависит от предыдущих операций и границы календарного окна. Классы и значения нужно выводить из точной формулировки: валюты, часового пояса, включительности границы и того, считаются ли отклонённые переводы.

Как превратить правило в данные
  1. Выяснить единицы денег и окно лимита.
  2. Разделить состояния по накопленной сумме.
  3. Проверить точную границу и один минимальный шаг за ней.
  4. Сверить ответ, баланс и историю.

Сначала запиши контракт, не выбирай числа наугад

Предположим, сумма хранится в копейках, лимит 10 000 RUB включительно, день считается по UTC, только успешные исходящие переводы входят в накопление. Если продукт считает день по часовому поясу клиента или курс конвертации используется на момент отправки, набор тестов меняется. Эти уточнения надо получить до багрепорта: иначе 10 000.01 может оказаться корректным по другому правилу.

Выдели классы по состоянию, а не только по параметру

Для нового пользователя первый перевод 1 RUB и 10 000 RUB — допустимые варианты; 0, отрицательное, дробная копейка и 10 000.01 — другие классы. Но если уже отправлено 9 999 RUB, перевод 1 RUB должен пройти, а 1.01 RUB — отклониться. Здесь граница относится к сумме «уже потрачено + новый перевод», а не к новому amount. Отдельно проверь, что отказ не списал деньги и не увеличил счётчик.

Проверь соседние временные точки

Операция непосредственно до 00:00 UTC и сразу после неё должна попасть в разные окна, если правило календарное. При rolling 24 hours ожидание другое. Запросы нужно фиксировать с точным временем и timezone, иначе тест невозможно повторить. Для автоматизации используй управляемые часы или подготовленное состояние, а не ожидание реальной полуночи. Летнее время важно, если окно задано локальной зоной, но не если оно строго UTC.

Проверь сочетание с конкурентностью

Два одновременных перевода по 6 000 RUB на пустом счётчике каждый отдельно выглядит допустимым, но вместе превышают лимит. Это уже не чистая BVA: нужен тест конкуренции и атомарности обновления счётчика. Запиши инвариант: успешных переводов за окно не больше 10 000 RUB суммарно. Разделение техник помогает не делать ложный вывод, будто пара обычных граничных запросов закрыла весь риск.

Минимальная матрица с ожидаемым эффектом
Spent  Request   Total      Expected\n0.00   10000.00  10000.00   accept; debit once\n0.00   10000.01  10000.01   reject; no debit\n9999   1.00      10000.00   accept\n9999   1.01      10000.01   reject; counter unchanged\n0.00   6000+6000 concurrent   at most one succeeds
Что добавить в тест-кейс?

Предусловие с накопленной суммой и часовым поясом, точную единицу денег, ожидаемый код и сообщение, проверку баланса, истории и счётчика после каждого отказа. Если контракт не отвечает, считать ли отменённый перевод частью лимита, запиши вопрос аналитикам, а не придуманный expected.

Второй кейс: промокод на корзину с двумя условиями

Условие акции: скидка 10% действует только на книги, если корзина не меньше 1 000 RUB и пользователь не применял этот код раньше. Проверка одного поля quantity здесь не поможет. Нужно выбрать сочетания условий, а затем границы суммы корзины и числа использований.

Выпиши условия независимо

У нас три фактора: стоимость подходящих книг относительно 1 000 RUB, наличие неподходящих товаров и история использования кода. Не подменяй “сумму корзины” суммой всех товаров без уточнения: возможно, порог считается только по книгам. Сначала спроси владельца правила, учитывается ли доставка и применяются ли возвраты к статусу “уже использован”. Без этого тест ожидаемой суммы может быть неверным.

Построй таблицу решений

Минимум нужны строки: книги ниже порога и код новый; ровно на пороге и код новый; выше порога и код уже использован; порог достигнут только за счёт неподходящего товара. Для каждой строки укажи accept/reject, итоговую скидку и изменение счётчика использования. Если условия независимы, проверь по одной строке, где каждое условие делает результат иным, а не все восемь комбинаций вслепую.

Найди границы с точностью до копейки

При пороге 1 000 RUB проверь 999.99, 1 000.00 и 1 000.01 для подходящих товаров. Если API хранит целые копейки, сравни 99999, 100000, 100001 в сырых данных. Отдельно проверь, округляется ли скидка на каждую позицию или на общую сумму: два товара по 5 копеек могут дать разный результат. Ожидаемую сумму вычисляй независимо от ответа.

Проверь состояние до и после отказа

Отклонённый код не должен помечаться использованным. Успешная покупка должна изменить состояние ровно один раз; отмена заказа может вернуть право на код только если это записано в правиле. После каждой попытки перечитай корзину и историю промокодов. Иначе тест может увидеть правильное сообщение об ошибке, но пропустить скрытое изменение лимита.

Добавь конкурентный запрос отдельно

Два параллельных checkout с одним одноразовым промокодом не должны оба получить скидку, если правило “один раз на пользователя”. Это тест состояния и атомарности, не BVA. Укажи инвариант “успешное применение не более одного” и сверяй не только ответы, но и финальные заказы и счётчик. Так видно, где заканчивается техника границ и начинается тестирование переходов.

Решения по условиям
Eligible books  Other goods  Used before  Expected\n999.99          0            no           reject\n1000.00         0            no           10% books\n900.00          200.00       no           contract decision\n1000.01         0            yes          reject; no counter change\n1000.00         0            no + no      only one parallel success
Какая техника нужна?

Классы и BVA для суммы, таблица решений для сочетания товара, порога и статуса кода, переходы состояния для повторного использования. У каждой техники свой вопрос; вместе они дают меньший и более обоснованный набор, чем десятки случайных корзин. Важно не объявлять каждую комбинацию граничным анализом. Если скидка зависит от роли покупателя, минимальной суммы и того, использован ли промокод, проверка 999,99/1000/1000,01 покрывает только порог. Она не докажет, что промокод нельзя применить второй раз и что сотрудник с запрещённой ролью не получит льготу. Для каждой строки таблицы решений фиксируй ожидаемую сумму и состояние кода после запроса; иначе ответ 422 может выглядеть правильным при ошибочно потраченном одноразовом коде. Когда правило округления неизвестно, не выбирай expected из текущего ответа сервера: уточни у владельца продукта порядок скидки, налогов и округления. Если валюта хранится в минимальных единицах, проверяй точные целые числа, а не результат вычисления с плавающей точкой. Повтори критичную строку через API и интерфейс, чтобы увидеть расхождение между расчётом и отображением.

Частые вопросы о границах

Всегда ли нужно брать три значения у границы?

Нет. Двухточечный BVA берёт границу и ближайшее значение соседнего класса; трёхточечный добавляет значение с другой стороны. Выбор зависит от риска и нужной силы покрытия.

Пустая строка и отсутствующее поле — один класс?

Не обязательно. Пустая строка передана явно, null может означать отсутствие значения, а missing запускает default или проверку required. Пока контракт не доказывает одинаковую обработку, проверяй их отдельно.

Можно ли применять границы к enum?

У enum нет естественного числового порядка, поэтому BVA обычно неприменим. Выдели допустимые и недопустимые классы, регистр, unknown value и отсутствие поля.

Техника запоминается не с первого прочтения, а после того, как одну и ту же границу проверишь в фильтре, в размере страницы и в суточном лимите — и увидишь, что это одна и та же проверка.

Потренироваться на живых сервисах Следующий разбор

← Все разборы