Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Валидация данных и схем
data_validation

Валидация данных и схем

Тестирование Pydantic моделей, JSON-схем, сериализации

Валидация данных и схем

«Автоматическая проверка что валидация работает для всех возможных входных данных.»

#Тестирование Pydantic моделей

#Базовые тесты валидации

from pydantic import BaseModel, EmailStr, Field, validator from hypothesis import given, strategies as st import pytest class User(BaseModel): email: EmailStr age: int = Field(ge=0, le=150) name: str = Field(min_length=1, max_length=100) @validator('name') def name_not_empty(cls, v): if v.strip() == '': raise ValueError('Name cannot be only whitespace') return v @st.composite def valid_user_strategy(draw): return { 'email': draw(st.emails()), 'age': draw(st.integers(min_value=0, max_value=150)), 'name': draw(st.text(min_size=1, max_size=100).filter(lambda s: s.strip() != '')) } @given(valid_user_strategy()) def test_valid_user_creation(user_data): user = User(**user_data) assert user.email == user_data['email'] assert user.age == user_data['age'] @st.composite def invalid_user_strategy(draw): """Генерирует невалидные данные для проверки валидации""" return { 'email': draw(st.emails()), 'age': draw(st.integers().filter(lambda x: x < 0 or x > 150)), 'name': draw(st.text(min_size=1, max_size=100)) } @given(invalid_user_strategy()) def test_invalid_user_age(user_data): with pytest.raises(ValueError): User(**user_data)

#Тестирование кастомных валидаторов

class Product(BaseModel): price: float quantity: int discount: float = Field(ge=0, le=1) @validator('price') def price_positive(cls, v): if v <= 0: raise ValueError('Price must be positive') return v @validator('discount') def discount_not_greater_than_price(cls, v, values): if 'price' in values and v * values['price'] > 1000: raise ValueError('Discount too large') return v @given( st.floats(allow_nan=False, allow_infinity=False), st.integers(), st.floats(min_value=0, max_value=1) ) def test_product_validation(price, quantity, discount): try: product = Product(price=price, quantity=quantity, discount=discount) # Если валидация прошла, проверяем инварианты assert product.price > 0 assert product.discount >= 0 assert product.discount <= 1 except ValueError: # Ожидаем ошибку для невалидных данных pass

#Тестирование JSON-схем

#Валидация JSON Schema

from jsonschema import validate, ValidationError import json SCHEMA = { "type": "object", "properties": { "id": {"type": "integer", "minimum": 1}, "email": {"type": "string", "format": "email"}, "age": {"type": "integer", "minimum": 0, "maximum": 150} }, "required": ["id", "email"] } @st.composite def valid_json_strategy(draw): return { "id": draw(st.integers(min_value=1, max_value=1000000)), "email": draw(st.emails()), "age": draw(st.integers(min_value=0, max_value=150)) } @given(valid_json_strategy()) def test_valid_json_schema(data): # Должно проходить валидацию validate(instance=data, schema=SCHEMA) @st.composite def invalid_json_strategy(draw): """Генерирует невалидный JSON для проверки схемы""" data = { "id": draw(st.integers().filter(lambda x: x < 1)), # Отрицательный ID "email": draw(st.text()), # Не email } # Иногда пропускаем обязательное поле if draw(st.booleans()): del data["email"] return data @given(invalid_json_strategy()) def test_invalid_json_schema(data): with pytest.raises(ValidationError): validate(instance=data, schema=SCHEMA)

#Round-trip тестирование сериализации

#JSON сериализация/десериализация

import json from datetime import datetime class DateTimeEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime): return obj.isoformat() return super().default(obj) @given(st.datetimes()) def test_datetime_roundtrip(dt): """Проверка что datetime сериализуется и десериализуется корректно""" # Сериализация json_str = json.dumps(dt, cls=DateTimeEncoder) # Десериализация loaded = datetime.fromisoformat(json.loads(json_str)) # Round-trip свойство assert loaded == dt @given(st.dictionaries(st.text(), st.integers())) def test_dict_roundtrip(original_dict): """Словарь сериализуется и десериализуется без потерь""" json_str = json.dumps(original_dict) loaded = json.loads(json_str) assert loaded == original_dict

#Pydantic сериализация

from pydantic import BaseModel class User(BaseModel): id: int email: str created_at: datetime class Config: json_encoders = { datetime: lambda v: v.isoformat() } @given( st.integers(min_value=1, max_value=1000000), st.emails(), st.datetimes() ) def test_user_serialization_roundtrip(user_id, email, created_at): user = User(id=user_id, email=email, created_at=created_at) # Сериализация json_str = user.json() loaded_dict = json.loads(json_str) # Десериализация user2 = User(**loaded_dict) # Round-trip свойство assert user2.id == user.id assert user2.email == user.email assert user2.created_at == user.created_at

#Заключение

Hypothesis автоматически находит баги в валидации данных, генерируя граничные значения и неожиданные входные данные.


Следующая тема: Performance тестирование — benchmarking, поиск узких мест, нагрузочное тестирование.

Далее: Performance и нагрузочное тестирование