Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Виды тестов в моей практике Python-разработчика
За 10+ лет я писал все основные виды тестов. Каждый тип имеет свою цель и место в стратегии тестирования.
Unit-тесты
Тесты для отдельных функций и методов без внешних зависимостей:
# tests/test_validators.py
import pytest
from app.validators import EmailValidator, InvalidEmailError
class TestEmailValidator:
@pytest.fixture
def validator(self):
return EmailValidator()
def test_valid_email(self, validator):
assert validator.validate('user@example.com') is True
def test_invalid_format(self, validator):
with pytest.raises(InvalidEmailError):
validator.validate('invalid-email')
def test_empty_string(self, validator):
with pytest.raises(InvalidEmailError):
validator.validate('')
Особенности: быстрые (< 100ms), изолированные, используют моки для внешних зависимостей.
Integration-тесты
Тесты взаимодействия нескольких компонентов, часто с БД:
# tests/integration/test_user_repository.py
import pytest
from app.models import User
from app.repositories import UserRepository
from sqlalchemy.orm import Session
@pytest.mark.integration
class TestUserRepository:
@pytest.fixture
def session(self, test_db):
"""Сессия для тестовой БД"""
return test_db.session
@pytest.fixture
def repository(self, session):
return UserRepository(session)
def test_create_and_retrieve_user(self, repository):
# Arrange
user_data = {'name': 'John', 'email': 'john@example.com'}
# Act
user = repository.create(user_data)
retrieved = repository.get_by_id(user.id)
# Assert
assert retrieved.name == 'John'
assert retrieved.email == 'john@example.com'
def test_update_user(self, repository):
user = repository.create({'name': 'John', 'email': 'john@example.com'})
repository.update(user.id, {'name': 'Jane'})
updated = repository.get_by_id(user.id)
assert updated.name == 'Jane'
def test_delete_user(self, repository):
user = repository.create({'name': 'John', 'email': 'john@example.com'})
repository.delete(user.id)
assert repository.get_by_id(user.id) is None
Особенности: медленнее (100ms-1s), требуют реальной БД или хорошего мока, проверяют интеграцию слоев.
E2E-тесты (End-to-End)
Полные тесты сценариев от пользователя через весь стек:
# tests/e2e/test_user_signup.py
import pytest
from fastapi.testclient import TestClient
from app.main import app
@pytest.mark.e2e
class TestUserSignup:
@pytest.fixture
def client(self):
return TestClient(app)
def test_user_signup_flow(self, client, test_db):
# 1. Пользователь регистрируется
response = client.post('/api/v1/auth/signup', json={
'email': 'newuser@example.com',
'password': 'SecurePass123'
})
assert response.status_code == 201
user_id = response.json()['id']
# 2. Пользователь логируется
response = client.post('/api/v1/auth/login', json={
'email': 'newuser@example.com',
'password': 'SecurePass123'
})
assert response.status_code == 200
token = response.json()['token']
# 3. Пользователь обновляет профиль
response = client.put(
f'/api/v1/users/{user_id}',
json={'name': 'John Doe'},
headers={'Authorization': f'Bearer {token}'}
)
assert response.status_code == 200
# 4. Проверяем сохранение
response = client.get(
f'/api/v1/users/{user_id}',
headers={'Authorization': f'Bearer {token}'}
)
assert response.json()['name'] == 'John Doe'
Особенности: самые медленные (1-10s), проверяют всю систему, требуют развернутого окружения.
API-тесты (HTTP-тесты)
Тесты для REST API эндпоинтов:
# tests/api/test_users_endpoints.py
import pytest
from fastapi.testclient import TestClient
@pytest.mark.api
class TestUserAPI:
@pytest.fixture
def client(self):
return TestClient(app)
@pytest.fixture
def auth_headers(self, client):
response = client.post('/api/v1/auth/login', json={
'email': 'admin@example.com',
'password': 'password'
})
token = response.json()['token']
return {'Authorization': f'Bearer {token}'}
def test_list_users(self, client, auth_headers):
response = client.get('/api/v1/users', headers=auth_headers)
assert response.status_code == 200
assert isinstance(response.json(), list)
def test_get_nonexistent_user(self, client, auth_headers):
response = client.get('/api/v1/users/999', headers=auth_headers)
assert response.status_code == 404
def test_unauthorized_access(self, client):
response = client.get('/api/v1/users')
assert response.status_code == 401
Load-тесты (тесты нагрузки)
Проверка производительности под нагрузкой с использованием locust или pytest-benchmark:
# tests/load/test_performance.py
import pytest
from locust import HttpUser, task, between
class LoadTestUser(HttpUser):
wait_time = between(1, 3)
@task
def list_users(self):
self.client.get('/api/v1/users')
@task(3)
def get_user(self):
self.client.get('/api/v1/users/1')
@task
def create_user(self):
self.client.post('/api/v1/users', json={
'email': f'user{random.randint(1,10000)}@example.com',
'name': 'Test User'
})
# Или с pytest-benchmark
def test_user_creation_performance(benchmark):
def create_user():
return User.objects.create(name='Test', email='test@example.com')
result = benchmark(create_user)
assert result.id is not None
Mock-тесты
Тесты с подменой внешних зависимостей:
# tests/test_email_service.py
from unittest.mock import patch, MagicMock
import pytest
@patch('app.services.email.SMTPClient')
def test_send_email(mock_smtp):
# Arrange
mock_smtp.return_value.send = MagicMock(return_value=True)
from app.services import EmailService
service = EmailService()
# Act
result = service.send_email('user@example.com', 'Hello')
# Assert
assert result is True
mock_smtp.return_value.send.assert_called_once()
Parametrized-тесты
Тесты с несколькими наборами данных:
import pytest
@pytest.mark.parametrize('email,is_valid', [
('valid@example.com', True),
('invalid.email', False),
('user@domain', False),
('test@test.co.uk', True),
])
def test_email_validation(email, is_valid):
validator = EmailValidator()
assert validator.validate(email) == is_valid
Test Coverage
Наше стандартное покрытие — минимум 90%:
# Проверить покрытие
pytest --cov=app --cov-report=html
# Результаты в htmlcov/index.html
Pyramid тестирования (правильное соотношение)
/\
/ \ E2E (5-10%)
/ \
/______\
/ \ Integration (20-30%)
/ \
/____________\
/ \ Unit tests (60-70%)
/ \
/__________________\
Вывод: я использую полный спектр тестирования — от быстрых unit-тестов до полных E2E сценариев. Правильное сочетание типов тестов обеспечивает надежность системы при минимизации времени выполнения.