Если RequestNewObject уже включает RequestPermissions как под-кейс, зачем последний нужен отдельной строкой в таблице?
Ку! Во второй части я взял 11 подтипов методов из таблицы части 1 и разложил каждый по полочкам: что за объект, чем рискует, какой тест-кейс ему обязателен и почему. С подтипами закрыли вопрос полностью.
Но таблица из первой части — это не только подтипы методов по вертикали. По горизонтали там ещё 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.
ссылка на оригинал статьи https://habr.com/ru/articles/1056364/