惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
GbyAI
GbyAI
Stack Overflow Blog
Stack Overflow Blog
Recent Announcements
Recent Announcements
D
DataBreaches.Net
量子位
C
Cybersecurity and Infrastructure Security Agency CISA
G
Google Developers Blog
小众软件
小众软件
Application and Cybersecurity Blog
Application and Cybersecurity Blog
MyScale Blog
MyScale Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Cisco Talos Blog
Cisco Talos Blog
P
Proofpoint News Feed
V
Vulnerabilities – Threatpost
Security Latest
Security Latest
T
Tenable Blog
Vercel News
Vercel News
AWS News Blog
AWS News Blog
Forbes - Security
Forbes - Security
M
MIT News - Artificial intelligence
S
Security Affairs
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Hacker News - Newest:
Hacker News - Newest: "LLM"
美团技术团队
L
Lohrmann on Cybersecurity
博客园 - 司徒正美
Last Week in AI
Last Week in AI
罗磊的独立博客
Project Zero
Project Zero
Cyberwarzone
Cyberwarzone
S
Schneier on Security
S
Secure Thoughts
T
Threatpost
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
Y
Y Combinator Blog
F
Full Disclosure
N
Netflix TechBlog - Medium
博客园 - 叶小钗
A
Arctic Wolf
腾讯CDC
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
The Exploit Database - CXSecurity.com
Jina AI
Jina AI
Apple Machine Learning Research
Apple Machine Learning Research
C
Check Point Blog
C
CERT Recently Published Vulnerability Notes
www.infosecurity-magazine.com
www.infosecurity-magazine.com

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Почему автотесты пропускают изменения в API и как это исправить с Pydantic
VladislavTim · 2026-04-23 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение9 мин

Охват и читатели146

Туториал

Приветствую, Хабр!

Меня зовут Владислав Тимашенков, я занимаюсь автоматизацией тестирования в ГК Infowatch.

Наша команда столкнулась с популярными болями автотестов для API:

  • одно изменение в API требует обновления нескольких тестов;

  • проверка структуры ответа распределена по тестам и не централизована;

  • валидация вложенных структур и генерируемых полей требует дополнительного кода.

И мы задались вопросом: какой инструмент для валидации контракта нам подойдёт?

В этой статье расскажем о нашем переосмыслении подхода к тестированию API с помощью внедрения Pydantic.

Рассмотрим простой пример теста на создание пользователя:

# ../your_project/tests/user/test_crud_user.py

 

def test_create_user():

    user_creation_body = {

        "USERNAME": "John_Smith",

        "EMAIL": "john@example.com",

        "DISPLAY_NAME": "John Smith",

    }

    response = requests.post(f"{SOME_URL}/user", json=user_creation_body)

    response.raise_for_status()

    data = response.json()

    assert data["USERNAME"] == user_creation_body["USERNAME"]

    assert data["EMAIL"] == user_creation_body["EMAIL"]

    assert data["DISPLAY_NAME"] == user_creation_body["DISPLAY_NAME"]

    assert isinstance(data["USER_ID"], int)

Тест проверяет успешность выполнения запроса и соответствие данных в конкретных полях. Но такой подход имеет ряд недостатков:

  • Нет проверки всей структуры данных ответа

  • Нет единого описания контракта

  • Дублирование assert (валидация типа для data["USER_ID"] нужна во всех тестах с участием сущности USER)

  • Изменения API могут пройти незаметно в непроверяемых полях

Поэтому мы решили выделить контракт API в отдельный слой валидации. Для этого нужен инструмент, который:

  • Валидирует структуру на основе единого контракта

  • Реализован независимо

  • Падает при расхождении с API

  • Внедряется и масштабируется поэтапно

Сначала мы попробовали Template и описывали ответы в виде эталонных json для сравнения структуры и значения ответа с заранее заданным шаблоном. Это частично закрывало потребности по структуре данных, но давало минимальные возможности по валидации полей и масштабированию.

Pydantic v2 решает эти задачи за счет строгой типизации, встроенной валидации и предсказуемого поведения, поэтому он лучше всего соответствовал нашим требованиям.

Запрос из теста в предыдущем примере

requests.post(f"{SOME_URL}/user", json=user_creation_body).json()

Вернет нам следующий json:

{

 "USER_ID": 69,

 "USERNAME": "John_Smith",

 "DISPLAY_NAME": "John Smith",

 "EMAIL": "john@example.com",

 "NOTE": null,

 "CREATE_DATE": "2026-01-01T00:01:30.897869",

 "STATUS": 1

}

Из документации к API проекта мы можем узнать требования к значениям полей. Создадим по ней Pydantic-модель для тела ответа. Модель описывает структуры данных с помощью аннотаций типов Python. Она определяет, какие поля ожидаются в ответе, их типы и ограничения, а также автоматически валидирует данные при создании объекта. Каждое поле является атрибутом модели с удобным вызовом.

# ../your_project/models/user/response.py

 

 

from datetime import datetime

from enum import IntEnum

from pydantic import BaseModel, ConfigDict, EmailStr, Field, PositiveInt

 

 

class UserStatuses(IntEnum):

   active = 1

   inactive = 0

 

 

class UserResponseModel(BaseModel):

  # Конфигурируем модель: задаем правила валидации и обработки полей.

  model_config = ConfigDict(

    extra="forbid",  # Запрет новых полей в ответе API.

    populate_by_name=True,  # Возможность использовать как alias полей, так и их имена.

    alias_generator=lambda field_name: field_name.upper() # Генерируем alias (UPPER_CASE) для сопоставления формата полей в модели и в API.

  )

 

  # Допускается только позитивное число

  user_id: PositiveInt

 

  # Строка длиной от 8 до 32 символов

  username: str = Field(min_length=8, max_length=32)

  display_name: str = Field(min_length=8, max_length=32)

 

  # Валидный формат email. Под капотом EmailStr либо email-validator.

  email: EmailStr

 

  # Необязательное поле.

  note: str | None = None

 

  # Дата и время в формате datetime.

  create_date: datetime

 

  # Значение из enum UserStatuses

  status: UserStatuses

Теперь в проекте есть модуль с описанием контракта для создания юзера. Он отвечает на вопрос: «Каким должен быть ответ?». По сути это исполняемая документация для API, клиентское описание контракта.

Обновленный тест проверяет структуру через pydantic-модель, а assert’ы отвечают только за бизнес-логику.

# ../your_project/tests/user/test_crud_user.py

 

from ..models.user.response import UserResponseModel

 

def test_create_user():

    user_creation_body = {

        "USERNAME": "John_Smith",

        "EMAIL": "john@example.com",

        "DISPLAY_NAME": "John Smith",

    }

    response = requests.post(f"{SOME_URL}/user", json=user_creation_body)

    response.raise_for_status()

    data = response.json()

 

    user_model = UserResponseModel.model_validate(data)

    

    # Поля из тела ответа - атрибуты Pydantic-модели

    assert user_model.USERNAME == user_creation_body["USERNAME"]

    assert user_model.EMAIL == user_creation_body["EMAIL"]

    assert user_model.DISPLAY_NAME == user_creation_body["DISPLAY_NAME"]

Теперь, если в поле “CREATE_DATE” вернется значение “31-03-2026” и добавится новое поле "IS_BLOCKED": false, то тест упадёт, даже если на эти поля нет assert. 

Для примера посмотрим сырое исключение ValidationError:

pydantic_core._pydantic_core.ValidationError: 2 validation errors for UserResponseModel

 

CREATE_DATE

  Input should be a valid datetime or date, invalid character in year [type=datetime_from_date_parsing, input_value='31-03-2026', input_type=str]

    For further information visit https://errors.pydantic.dev/2.11/v/datetime_from_date_parsing

 

IS_BLOCKED

  Extra inputs are not permitted [type=extra_forbidden, input_value=False, input_type=bool]

    For further information visit https://errors.pydantic.dev/2.11/v/extra_forbidden

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

Например, NotificationModel использует уже готовые модели UserResponseModel и TemplateModel:

# ../your_project/models/notifications/response.py

from datetime import datetime

from ..models.user.response import UserResponseModel

from ..models.templates.response import TemplateResponseModel

from pydantic import BaseModel

 

class NotificationResponseModel(BaseModel):

    model_config = ConfigDict(

      extra="forbid",

      populate_by_name=True,

      alias_generator=lambda field_name: field_name.upper()

   )   

   notification_id: str

   display_name: str

   create_date: datetime

   # Автоматические рекурсивные валидации по каждому элементу списка

   recipients: list[UserResponseModel]

   templates: list[TemplateResponseModel]

А UserResponseModel участвует и в создании NotificationResponseModel, и в тесте test_add_notification без дублирования кода. Любое изменение в Pydantic-модели автоматически задействовано во всех тестах и моделях, которые её используют.

# ../your_project/tests/notification/test_add_notification.py

 

from ..models.notifications.response import NotificationResponseModel

 

 

def test_add_notification():

    notification_add_body = {

        "DISPLAY_NAME": "John Smith",

        "RECIPIENTS": [..],

        "TEMPLATES": [..],

    }

    response = requests.post(f"{SOME_URL}/notification", json=notification_add_body)

    data = response.json()

    notification_model = NotificationResponseModel.model_validate(data)

    assert notification_model.DISPLAY_NAME == notification_add_body["DISPLAY_NAME"]

Наш опыт внедрения подхода с Pydantic

Мы декомпозировали эпик на задачи и поэтапно реализовали поддержку Pydantic во фреймворке проекта, описали наиболее важные «ручки» для сложных сущностей, добавляли модели при изменениях существующих тестов и выносили повторяющиеся структуры в общие модели.

Не пришлось переписывать все и сразу, на одну «ручку» приходится примерно 1-3 модели в зависимости от структуры. Отличным инструментом для ускорения генерации моделей стал datamodel-code-generator. По OpenAPI-спецификации он в один вызов генерирует модели, которые затем можно привести к стилю проекта и дополнить валидацией. Пример:

datamodel-codegen \

  --input your_openapi_spec.yaml \             # путь к исходной OpenAPI-спецификации

  --input-file-type openapi \                  # указываем тип спецификации

  --output your_path/ \                        # путь для генерации моделей

  --target-python-version 3.13 \               # версия Python для использования актуального синтаксиса

  --output-model-type pydantic_v2.BaseModel \  # использовать Pydantic v2 BaseModel как базовый класс моделей

  --snake-case-field \                         # конвертировать имена полей в snake_case

  --use-double-quotes \                        # использовать двойные кавычки в сгенерированном коде

  --use-schema-description \                   # переносить описание из схемы в docstring или Field(description=...)

  --formatters ruff-check ruff-format          # проверять код через Ruff (линтеры + автоформатирование)

Примерно за полгода удалось написать 280+ моделей, которые закрыли 1740+ полей и покрыли большую часть контракта. Очень ценным побочным эффектом стало то, что мы перетрясли API продукта и обнаружили ряд неочевидных и ранее незамеченных проблем: несовпадение типов, неожиданные значения, поля и логики, отсутствующие в документации.

Как итог, мы изменили сам подход к тестированию API ключевого продукта InfoWatch:

  • Проверки разделены на два независимых слоя: валидации структуры ответа и валидацию бизнес-логики.

  • Автоматически и централизованно отслеживаем изменения в API

  • Сократили количество assert в тестах за счёт переноса проверок структуры в модели

  • Увеличили покрытие за счет переиспользования моделей

Этот подход особенно хорошо масштабируется в больших проектах, где API активно развивается.