Если RequestNewObject уже включает RequestPermissions как под-кейс, зачем последний нужен отдельной строкой в таблице?
Ку! Во второй части я взял 11 подтипов методов из таблицы части 1 и разложил каждый по полочкам: что за объект, чем рискует, какой тест-кейс ему обязателен и почему. С подтипами закрыли вопрос полностью.
Автотесты: что есть 100% покрытие API? Часть 2. Подтипы методов
Автотесты: что есть 100% покрытие API? Часть 3. Типы тест-кейсов
Но таблица из первой части — это не только подтипы методов по вертикали. По горизонтали там ещё 12 типов тест-кейсов, и с ними та же болезнь: в таблице они лежат ровным списком одного уровня — просто строчки "применимо / приоритет". А по факту не ровным: часть тест-кейсов явно пересекается по смыслу, часть — вложена одна в другую через SubTestCases, и если не разбирать почему, легко сделать ровно ту ошибку, из-за которой всё это затевалось — посчитать, что раз тест-кейс формально есть в таблице, то покрытие есть. Взять хотя бы RequestNewObject: среди его под-кейсов прямо в таблице части 1 указан RequestPermissions. Значит ли это, что RequestPermissions как отдельная строка — просто дубль? Нет, не дубль, и вот в этом различии — весь смысл сегодняшнего разбора.
Поэтому в этой части — все 12 типов тест-кейсов, но не по порядку номеров из таблицы, а по вопросам, на которые каждый отвечает. Так сразу видно, где заканчивается один тест-кейс и начинается другой — включая пару из примера выше.
Полный список — шпаргалка
Раскрыть список
№ | Тест-кейс | Суть в одну строку |
|---|---|---|
1 |
| Эндпоинт жив, ответ по форме соответствует сваггеру и схеме |
2 |
| Ответ не просто похож по форме, а совпадает по конкретным значениям с эталоном |
3 |
| Ролевая модель: без прав — нет доступа, с правами — есть, отдельно объект и отдельно эндпоинт |
4 |
| Состояние корректно распространилось сразу после создания объекта |
5 |
| Состояние корректно распространилось сразу после обновления объекта (включая кэш) |
6 |
| Транспортно кривые данные (битый JSON, не тот формат) дают чистую ошибку без утечки |
7 |
| Объект виден именно там, где должен (админка/паблик), и не виден там, где не должен |
8 |
| Фильтрация, сортировка, пагинация списка работают корректно |
9 |
| Валидный JSON, но конкретное поле — на границе или вне контракта (обязательность, длина, тип, диапазон) |
10 |
| Корректный метод, отсутствующий/удалённый ресурс — правильная 404-ветка |
11 |
| Лишние поля/заголовки/параметры, которых не должно быть в контракте, не меняют поведение и не текут в ответ |
12 |
| Существующий ресурс, неподдерживаемый метод — правильная ветка роутинга |
"Работает ли вообще и то ли отдаёт": RequestDefault и RequestCompareBenchmark
Разница между ними — не в теме, а в глубине. RequestDefault — это "эндпоинт жив, статус 200, тело хотя бы по форме похоже на ожидаемое" (сверка со сваггером плюс валидация схемы, без залезания в конкретные значения). RequestCompareBenchmark идёт на уровень глубже: не просто "форма похожа", а "значения именно те, что мы ожидали".
Это два теста, а не один, потому что они ловят разные классы багов на разной скорости обратной связи: RequestDefault падает за секунды и говорит "тут вообще всё сломано", RequestCompareBenchmark требует подготовленных эталонных данных и говорит "форма цела, но логика перепутала значения". Если слить их в один тест-кейс, вы либо утяжеляете дефолтный smoke-тест подготовкой эталонов (и он перестаёт быть быстрым и дефолтным), либо теряете точную сверку значений внутри "общего" теста.
"Кто имеет право" и "что происходит в момент изменения": RequestPermissions, RequestNewObject, RequestUpdateObject
Ответ на вопрос из начала части.
RequestPermissions — горизонтальная проверка. Она отвечает на вопрос "в устойчивом состоянии, правильно ли работает ролевая модель для этого объекта или эндпоинта": нет доступа без прав, есть доступ с правами, отдельно для объекта, отдельно для эндпоинта. Она не привязана к тому, когда объект появился — хоть вчера, хоть год назад.
RequestNewObject — это не проверка прав. Это проверка момента: правильно ли распространилось состояние сразу после создания. И да, среди её под-кейсов есть Sub.TC: RequestPermissions — но это не повтор той же проверки, а проверка той же логики прав в конкретный рискованный момент времени, когда она чаще всего и ломается.
Пример, почему это разделение — не бюрократия, а необходимость: Комментарий к посту. Обычный RequestPermissions, прогнанный на заранее засеянном (seed) комментарии, который существует в системе уже давно — зелёный, права работают. А теперь RequestNewObject: создаём комментарий прямо в тесте и в ту же секунду пытаемся получить к нему доступ от имени автора и от имени постороннего. И вот тут вылезает race condition — пока создание комментария асинхронно прописывает permissions в отдельном сервисе прав, объект уже виден по GET, но с дефолтными (слишком широкими) правами, потому что запись в сервис прав ещё не долетела. Через 200 миллисекунд всё встанет на место, RequestPermissions на давно существующем объекте никогда это не поймает — он тестирует состояние, которое уже "устоялось". А RequestNewObject тестирует именно момент, когда оно ещё не устоялось.
Отсюда практическое правило: если у вас в наборе автотестов RequestPermissions всегда гоняется по заранее заготовленным фикстурным объектам "для скорости" — вы формально закрыли строчку в таблице покрытия, но не закрыли самый частый в реальности класс багов прав доступа. Именно поэтому в регламенте это два разных тест-кейса, а не один с галочкой "права проверены".
Та же логика — для RequestUpdateObject. Тест-кейс проверяет не "работает ли обновление вообще" (это уже сделал RequestDefault на уровне метода), а "корректно ли распространилось состояние после конкретного обновления" — включая сброс кэша, что явно упомянуто в описании тест-кейса в первой части. Обновили объект — а в списке (который закэширован на минуту) он всё ещё старый. Это баг именно RequestUpdateObject, а не общий баг метода GET LIST OBJECTS, потому что сам список работает правильно — не работает инвалидация кэша именно в момент изменения.
"Данные кривые" и "данные на грани": RequestIncorrectBody и RequestElements
RequestIncorrectBody — это уровень транспорта и общей формы: сломанный JSON, неправильный Content-Type, структура, которая вообще не парсится как ожидаемый объект. Главный критерий успеха — чистая ошибка без утечки чувствительных данных (это прямым текстом написано в первой части, и это не случайно: сломанный парсинг — частое место, где в ответ по ошибке улетает стектрейс с внутренними путями или кусками другого запроса).
RequestElements — это уже валидный, парсящийся JSON, где мы прицельно меняем один конкретный элемент по конкретному правилу: убрали обязательное поле, дали граничное значение, превысили лимит длины строки, подменили тип данных для ключа. Это не "форма сломана", а "форма цела, но значение одного поля выходит за контракт".
Граница простая: если ломается JSON или структура целиком — это RequestIncorrectBody. Если JSON валиден, но одно поле не проходит бизнес-валидацию — это RequestElements. Смешивать их в один тест-кейс вредно практически: RequestIncorrectBody пишется один раз на метод (набор кейсов почти не зависит от конкретных полей объекта), а RequestElements обязан прогоняться на каждое поле отдельно — это два совершенно разных по трудоёмкости и частоте обновления набора тестов.
"Данных не хватает" и "данных больше, чем нужно": RequestElements и RequestExtraData
RequestElements про недостаток или искажение того, что уже есть в контракте. А RequestExtraData — про появление того, чего в контракте нет вообще: лишний заголовок, лишний параметр, лишний ключ в теле, которого нет в схеме.
Казалось бы, зачем отдельный тест-кейс на "мы просто добавили лишнее поле, всем должно быть всё равно". Затем, что "всем должно быть всё равно" — это гипотеза, а не факт, и именно она чаще всего не подтверждается. Частый реальный баг: ORM на бэкенде замаплена так, что лишний ключ в теле POST-запроса, случайно совпавший по имени с внутренним служебным полем (например, is_admin или owner_id), молча меняет поведение создаваемого объекта, потому что фреймворк сериализации не отбрасывает неизвестные поля, а прозрачно кладёт их во внутреннюю модель. Это классический mass assignment — и он ловится ровно тем тест-кейсом, который в таблице первой части имеет самый низкий приоритет (3), хотя по последствиям может быть куда серьёзнее, чем RequestNotAllowed, у которого тот же третий приоритет.
Вывод, который стоит явно занести в уточнения к регламенту: приоритет тест-кейса в таблице — это приоритет по трудоёмкости внедрения и типичной частоте срабатывания, а не по тяжести возможных последствий. RequestExtraData дёшево не писать, но дорого пропустить — и это стоит учитывать при планировании, кому из тест-кейсов третьего приоритета выделить время в первую очередь.
"Правильный ли контекст показа": RequestEnvList
RequestEnvList стоит особняком: он не про создание/обновление и не про права в смысле ролей, а про то, что один и тот же объект может и должен выглядеть по-разному (или не выглядеть вовсе) в разных срезах системы — в админке видно всё, в публичном API — только опубликованное. Это отдельный вопрос от прав доступа: пользователь может иметь полное право видеть объект, но объект может быть недоступен в конкретном представлении по бизнес-правилу (например, черновик просто не публикуется, вне зависимости от прав того, кто спрашивает).
Именно поэтому RequestEnvList фигурирует и как самостоятельный тест-кейс, и как под-кейс внутри RequestNewObject/RequestUpdateObject — как самостоятельный он проверяет правило видимости в принципе, как под-кейс — что это правило не сломалось в момент конкретного изменения состояния объекта.
"Правильно ли ведёт себя список": RequestsParams
RequestsParams целиком про GET LIST OBJECTS и POST FILTER/SORT/OBJECTS ON LIST — фильтрация, сортировка, пагинация и их комбинации. Пересечение с RequestElements возникает именно у POST-варианта: параметры списка передаются в теле, и вопрос "а что если передать некорректное значение параметра фильтра" по механике похож на RequestElements.
Разница в намерении теста, а не в форме запроса: RequestsParams проверяет функциональную корректность эффекта параметра на возвращаемый набор данных ("отфильтровало ли по вот этому значению верно"). RequestElements, применённый к тому же полю в том же запросе, проверяет контракт самого поля ("что будет, если тип значения неверный или значение выходит за границы"). Формально можно писать оба теста над одним и тем же полем тела запроса — и это нормально, они отвечают на разные вопросы, а не дублируют друг друга.
"Ресурса нет" и "метод не поддерживается": RequestNotFound и RequestNotAllowed
Оба дают ошибочный статус, оба на практике часто пишутся "для галочки" с минимальной проверкой (третий/второй приоритет), но проверяют принципиально разные ветки роутинга.
RequestNotFound — правильный метод, неправильный или отсутствующий идентификатор ресурса. RequestNotAllowed — правильный ресурс (маршрут существует), неподдерживаемый метод на нём (например, DELETE на эндпоинте, который только читает). Разные баги ловятся по-разному: RequestNotFound ловит некорректную обработку несуществующих/удалённых id (в том числе — повторное обращение к уже удалённому объекту, разобрали во второй части на примере DELETE OBJECT). RequestNotAllowed ловит дыры в конфигурации роутинга — например, случайно открытый DELETE на продовом окружении там, где по документации метод не должен существовать вообще. Это не гипотетический риск: конфигурация роутов копируется между окружениями, и там, где на деве DELETE специально включили для дебага, на проде он иногда остаётся включённым по забывчивости — тест RequestNotAllowed это ловит, а RequestDefault — нет, потому что RequestDefault просто не пробует вызвать неподдерживаемый метод.
Сводка: горизонтальные и вертикальные тест-кейсы
Из разбора выше следует одно уточнение к регламенту первой части, которое стоит внести явно: часть тест-кейсов горизонтальны — они проверяют свойство системы в устойчивом состоянии независимо от истории объекта (RequestPermissions, RequestsParams, RequestEnvList, RequestNotFound, RequestNotAllowed, RequestIncorrectBody, RequestExtraData). А часть — вертикальны, это обёртки над моментом изменения состояния (RequestNewObject, RequestUpdateObject), которые переиспользуют горизонтальные тест-кейсы как под-кейсы, но проверяют их именно в переходный момент, где и живёт большая часть трудноуловимых багов.
Разделение кейсов по группам
Тест-кейс | Группа |
|---|---|
RequestDefault | Работает ли вообще |
RequestCompareBenchmark | Работает ли вообще |
RequestPermissions | Права (горизонтально) |
RequestNewObject | Момент изменения (вертикально) |
RequestUpdateObject | Момент изменения (вертикально) |
RequestIncorrectBody | Кривые данные |
RequestElements | На грани контракта / недостаток данных |
RequestExtraData | Лишние данные |
RequestEnvList | Контекст показа |
RequestsParams | Поведение списка |
RequestNotFound | Ресурса нет |
RequestNotAllowed | Метод не поддерживается |
Это разделение стоит держать в голове при построении матрицы покрытия: закрыть горизонтальный тест-кейс один раз на стабильном объекте недостаточно, если у эндпоинта есть создание или обновление — переходный момент нужно проверять отдельно, и именно поэтому в таблице первой части RequestNewObject/RequestUpdateObject идут с пометкой первого приоритета, а не как опциональное дополнение к уже написанному RequestPermissions.
