Как работать с JSON в автотестах
Что проверяют
Умеете ли проверять ответ по структуре, а не сравнением строк, и как обходитесь с полями, которые меняются от запуска к запуску.
Как отвечать
Строка это транспорт, проверять надо разобранный объект. В Python response.json() или json.loads, в Java Jackson или Gson. Сравнение сырых строк ложно падает от порядка ключей, лишних пробелов и экранирования Unicode, а на реальную поломку контракта не реагирует.
Три уровня проверок:
- Схема. Типы, обязательные поля, форматы, диапазоны. JSON Schema и библиотека
jsonschemaв Python, моделиpydantic,json-schema-validatorв Java. Схема ловит именно то, что точечные assert пропускают: исчезнувшее поле, число вместо строки,nullтам, где его быть не должно. - Значения бизнес-полей. Точечные утверждения по конкретным ключам.
- Контракт целиком. Снапшот ответа с маскированием изменчивых полей.
from jsonschema import validate
schema = {
"type": "object",
"required": ["id", "name", "is_active"],
"properties": {
"id": {"type": "integer"},
"name": {"type": "string", "minLength": 1},
"is_active": {"type": "boolean"},
},
"additionalProperties": False,
}
validate(instance=response.json(), schema=schema)Динамические поля (id, created_at, токены, подписи) сравнивать по значению нельзя. Их либо исключают из сравнения (deepdiff с exclude_paths), либо проверяют по типу и формату.
Адресация вложенности. Для глубоких структур удобнее JSONPath (jsonpath-ng в Python, JsonPath в Java), чем цепочка data["a"]["b"][0], которая при отсутствии ключа падает с KeyError без внятного сообщения о том, что именно сломалось.
Негативные сценарии обязательны: битый JSON, обрезанный ответ, пустое тело, неверный Content-Type. json.loads бросает json.JSONDecodeError (наследник ValueError), и тест должен ожидать конкретное исключение.
Тестовые данные держать в отдельных .json файлах и подтягивать фикстурами, а не хранить многострочными литералами в коде: так их проще версионировать, переиспользовать и diff читается человеком.
Ловушки формата:
- ключи в JSON всегда строки, поэтому словарь с числовыми ключами после сериализации и обратного разбора вернётся со строковыми;
- числа это double, длинные идентификаторы безопаснее передавать строкой, иначе теряется точность;
True/False/Noneв Python противtrue/false/nullв JSON;json.dumpsпо умолчанию экранирует кириллицу, для читаемых логов нуженensure_ascii=False.
Где спотыкаются
Сравнивают ответ как строку или пишут assert на пару полей, и тест молча пропускает исчезнувшее поле. Ещё забывают, что порядок элементов массива в JSON значим, а порядок ключей в объекте нет, и строят сравнение не на том.