Тестирование Pydantic моделей, JSON-схем, сериализации
«Автоматическая проверка что валидация работает для всех возможных входных данных.»
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:
# Ожидаем ошибку для невалидных данных
passfrom 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)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_dictfrom 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_atHypothesis автоматически находит баги в валидации данных, генерируя граничные значения и неожиданные входные данные.
Следующая тема: Performance тестирование — benchmarking, поиск узких мест, нагрузочное тестирование.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.