ТестированиеОсновы тестированияИнженер по тестированию

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

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

function accessLevel(role) {
  if (role === "admin") return "full";
  if (role === "editor") return "limited";
  return "denied";
}

const roles = ["admin", "editor", "guest", ""];
Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Для этой функции достаточно выделить три класса эквивалентности: admin, editor и все остальные значения, приводящие к результату denied. Минимальный набор представителей: "admin", "editor" и, например, "guest".

Класс эквивалентности объединяет входы, для которых ожидается одинаковое наблюдаемое поведение. Такой набор сокращает число тестов, но корректен только при предположении, что значения внутри одного класса действительно обрабатываются одинаково.

Исторический контекст

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

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

Постановка проблемы

Если проверить только "admin" и "editor", можно пропустить ошибку в обработке неизвестной роли. Если проверять десятки разных неизвестных строк по отдельности, набор будет избыточным при условии, что все они должны давать denied.

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

Подробное решение

Разбиение строится по ожидаемому результату и правилам контракта:

  • "admin" — возвращается full;
  • "editor" — возвращается limited;
  • любое другое допустимое значение роли — возвращается denied.

Из каждого класса выбирают минимум одного представителя. Поэтому "admin", "editor" и "guest" покрывают три различные реакции функции. Значения "viewer", "unknown" и "visitor" не добавляют нового покрытия, если контракт действительно считает их одной категорией.

function accessLevel(role) { if (role === "admin") return "full"; if (role === "editor") return "limited"; return "denied"; } console.assert(accessLevel("admin") === "full"); console.assert(accessLevel("editor") === "limited"); console.assert(accessLevel("guest") === "denied");

Важно различать класс эквивалентности и отдельный путь выполнения. В текущей реализации "guest" и "" дают одинаковый результат, однако это не делает их автоматически одним бизнес-классом: требования могут предписывать разные реакции для неизвестной и пустой роли.

Метод имеет ограничение: он не гарантирует обнаружение ошибок внутри класса. Например, дефект, затрагивающий только строку "viewer", не будет найден тестом с "guest". Поэтому представители выбираются с учётом риска, а разбиение дополняется проверками границ, формата, обязательности и правил домена.

Ситуация из практики

В системе были роли admin, editor, viewer и неизвестные значения. Рассматривались три варианта. Полный перебор всех строк был практически невозможен; один тест на успешную роль не проверял отказ; по одному тесту на каждое известное значение давал хорошее покрытие, но не проверял неизвестный ввод.

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

Что кандидаты часто упускают

1. Достаточно ли одного представителя для класса эквивалентности?

Нет, это рабочая гипотеза, а не доказательство одинакового поведения всех значений. Дополнительные представители нужны, если класс широк, имеет разные форматы, повышенный риск или известную историю дефектов. Например, для класса неизвестных ролей можно отдельно проверить обычную строку, строку с пробелами и значение с неожиданным регистром, если такие входы разрешены контрактом.

2. Нужно ли объединять пустое значение и неизвестную роль?

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

3. Чем эквивалентное разбиение отличается от проверки границ?

Разбиение выбирает типичных представителей областей с одинаковым ожидаемым поведением. Анализ граничных значений специально проверяет точки около смены поведения, где вероятность ошибок выше. Для числового диапазона можно взять один представитель внутреннего класса, но дополнительно проверить значения непосредственно до границы, на границе и сразу после неё.