Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Какие тесты я умею писать
Тестирование — это искусство и наука. Я имею опыт написания различных видов тестов и понимаю когда и какой тип использовать. Давайте разберемся во всех видах тестов.
Пирамида тестов
E2E (End-to-End) - 5-10%
/\
/ \
/ \ Integration Tests - 15-20%
/ \
/ \ Unit Tests - 70-80%
1. Unit Tests (Модульные тесты)
Цель: Тестировать отдельные функции/методы в изоляции
import pytest
from unittest.mock import Mock, patch
from myapp.services import UserService
from myapp.models import User
class TestUserService:
"""Тесты для UserService"""
@pytest.fixture
def user_service(self):
"""Fixture для создания UserService"""
return UserService()
def test_create_user_success(self, user_service):
"""Успешное создание пользователя"""
# Arrange
user_data = {"email": "john@example.com", "name": "John"}
# Act
result = user_service.create_user(**user_data)
# Assert
assert result.email == "john@example.com"
assert result.name == "John"
def test_create_user_invalid_email(self, user_service):
"""Проверка валидации email"""
with pytest.raises(ValueError, match="Invalid email"):
user_service.create_user(email="invalid-email", name="John")
def test_get_user_by_id(self, user_service):
"""Получение пользователя по ID"""
# Создаем тестовые данные
user = user_service.create_user(email="jane@example.com", name="Jane")
# Получаем пользователя
fetched = user_service.get_user(user.id)
# Проверяем
assert fetched.id == user.id
assert fetched.email == "jane@example.com"
def test_delete_user(self, user_service):
"""Удаление пользователя"""
user = user_service.create_user(email="temp@example.com", name="Temp")
user_id = user.id
user_service.delete_user(user_id)
with pytest.raises(User.DoesNotExist):
user_service.get_user(user_id)
2. Тесты с мокированием (Mocking)
Цель: Изолировать компонент подменяя его зависимости
from unittest.mock import Mock, patch, MagicMock
from myapp.services import PaymentService
from myapp.external.payment_gateway import StripeGateway
class TestPaymentService:
"""Тесты для платежного сервиса"""
def test_process_payment_with_mock(self):
"""Тестирование с мокированным Stripe"""
# Создаем mock
mock_gateway = Mock(spec=StripeGateway)
mock_gateway.charge.return_value = {'status': 'success', 'charge_id': '123'}
# Внедряем mock
service = PaymentService(gateway=mock_gateway)
# Выполняем операцию
result = service.process_payment(amount=100, card_token='tok_visa')
# Проверяем результат
assert result['status'] == 'success'
# Проверяем что метод был вызван с правильными параметрами
mock_gateway.charge.assert_called_once_with(100, 'tok_visa')
def test_payment_retry_on_failure(self):
"""Проверка retry логики"""
mock_gateway = Mock(spec=StripeGateway)
# Первый вызов - ошибка, второй - успех
mock_gateway.charge.side_effect = [
Exception("Temporary error"),
{'status': 'success', 'charge_id': '456'}
]
service = PaymentService(gateway=mock_gateway)
# Операция должна переделаться
result = service.process_payment_with_retry(amount=100, card_token='tok_visa')
assert result['status'] == 'success'
assert mock_gateway.charge.call_count == 2
3. Integration Tests (Интеграционные тесты)
Цель: Тестировать взаимодействие нескольких компонентов
import pytest
from django.test import TestCase, Client
from myapp.models import User, Order
class TestOrderIntegration(TestCase):
"""Интеграционные тесты для заказов"""
def setUp(self):
"""Подготовка перед каждым тестом"""
self.user = User.objects.create_user(
email='test@example.com',
password='password123'
)
self.client = Client()
def test_create_order_workflow(self):
"""Полный процесс создания заказа"""
# 1. Авторизация
self.client.login(email='test@example.com', password='password123')
# 2. Создание заказа
response = self.client.post('/api/v1/orders/', {
'items': [{'product_id': 1, 'quantity': 2}],
'shipping_address': {'city': 'Moscow'}
}, content_type='application/json')
# 3. Проверяем ответ
assert response.status_code == 201
order_id = response.json()['id']
# 4. Проверяем БД
order = Order.objects.get(id=order_id)
assert order.user == self.user
assert order.total_items == 2
# 5. Проверяем статус
response = self.client.get(f'/api/v1/orders/{order_id}/')
assert response.json()['status'] == 'pending'
4. E2E Tests (End-to-End тесты) с Playwright
Цель: Тестировать полный пользовательский сценарий через браузер
import pytest
from playwright.sync_api import sync_playwright, expect
class TestUserRegistrationFlow:
"""E2E тесты для регистрации пользователя"""
@pytest.fixture
def browser(self):
"""Фикстура браузера"""
with sync_playwright() as p:
browser = p.chromium.launch()
yield browser
browser.close()
def test_user_can_register_and_login(self, browser):
"""Пользователь может зарегистрироваться и войти"""
page = browser.new_page()
# 1. Переходим на страницу регистрации
page.goto('http://localhost:3000/register')
# 2. Заполняем форму
page.fill('input[name="email"]', 'newuser@example.com')
page.fill('input[name="password"]', 'SecurePass123!')
page.fill('input[name="confirm_password"]', 'SecurePass123!')
# 3. Отправляем форму
page.click('button[type="submit"]')
# 4. Ожидаем редирект
page.wait_for_url('**/login')
# 5. Логинимся
page.fill('input[name="email"]', 'newuser@example.com')
page.fill('input[name="password"]', 'SecurePass123!')
page.click('button[type="submit"]')
# 6. Проверяем что вошли в систему
expect(page).to_have_url('**/dashboard')
expect(page.locator('text=Welcome, newuser')).to_be_visible()
page.close()
5. Snapshot Tests (Тесты снимков)
Цель: Проверить что вывод не изменился
import pytest
from myapp.serializers import UserSerializer
def test_user_serialization_snapshot(snapshot):
"""Проверить формат сериализации юзера"""
user = User(id=1, email='john@example.com', name='John')
serializer = UserSerializer(user)
# Сравниваем с сохраненным снимком
assert serializer.data == snapshot
# Первый запуск создает файл:
# tests/__snapshots__/test_serializers.ambr
# При изменении кода - тест ломается, нужно обновить
6. Parametrized Tests (Параметризованные тесты)
Цель: Тестировать одну функцию с разными данными
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("WORLD", "WORLD"),
("123", "123"),
("", ""),
])
def test_uppercase(input, expected):
"""Тестировать функцию с разными значениями"""
assert input.upper() == expected
# Запустится 4 теста автоматически
@pytest.mark.parametrize("user_id,should_exist", [
(1, True),
(999, False),
(0, False),
])
def test_user_exists(user_id, should_exist):
exists = User.objects.filter(id=user_id).exists()
assert exists == should_exist
7. API Tests (Тесты REST API)
Цель: Тестировать HTTP endpoints
import pytest
from rest_framework.test import APIClient
from django.contrib.auth.models import User
class TestUserAPI:
"""Тесты для User API endpoints"""
@pytest.fixture
def api_client(self):
return APIClient()
@pytest.fixture
def user(self):
return User.objects.create_user(
username='testuser',
email='test@example.com',
password='pass123'
)
def test_list_users(self, api_client):
"""GET /api/users/"""
response = api_client.get('/api/v1/users/')
assert response.status_code == 200
assert isinstance(response.json(), list)
def test_create_user(self, api_client):
"""POST /api/users/"""
data = {
'email': 'new@example.com',
'password': 'pass123'
}
response = api_client.post('/api/v1/users/', data)
assert response.status_code == 201
assert response.json()['email'] == 'new@example.com'
def test_get_user_detail(self, api_client, user):
"""GET /api/users/1/"""
response = api_client.get(f'/api/v1/users/{user.id}/')
assert response.status_code == 200
assert response.json()['email'] == 'test@example.com'
def test_update_user(self, api_client, user):
"""PUT /api/users/1/"""
api_client.force_authenticate(user=user)
response = api_client.put(f'/api/v1/users/{user.id}/', {
'email': 'updated@example.com'
})
assert response.status_code == 200
8. Property-Based Tests (Генеративное тестирование)
Цель: Тестировать с автоматически сгенерированными данными
from hypothesis import given, strategies as st
@given(st.integers())
def test_absolute_value_is_positive(x):
"""Абсолютное значение всегда положительное"""
assert abs(x) >= 0
@given(st.lists(st.integers()))
def test_sorted_list_is_sorted(lst):
"""Отсортированный список всегда отсортирован"""
sorted_lst = sorted(lst)
assert all(sorted_lst[i] <= sorted_lst[i+1] for i in range(len(sorted_lst)-1))
@given(
st.emails(),
st.text(min_size=1, max_size=100)
)
def test_user_creation_with_generated_data(email, name):
"""Создание пользователя с сгенерированными данными"""
user = User.objects.create_user(email=email, name=name)
assert user.email == email
assert user.name == name
9. Performance Tests (Тесты производительности)
Цель: Убедиться что операция быстрая
import pytest
import time
from django.test.utils import override_settings
class TestPerformance:
"""Тесты производительности"""
@pytest.mark.benchmark
def test_large_query_performance(self):
"""Тест скорости выполнения сложного запроса"""
start = time.time()
users = User.objects.filter(is_active=True).select_related('profile')[:1000]
list(users) # Force evaluation
elapsed = time.time() - start
# Должно выполниться менее чем за 100ms
assert elapsed < 0.1
def test_cache_effectiveness(self):
"""Проверка что кэш работает"""
# Первый запрос - медленно
start = time.time()
user1 = get_user_cached(1)
time1 = time.time() - start
# Второй запрос - из кэша, должен быть быстрее
start = time.time()
user2 = get_user_cached(1)
time2 = time.time() - start
assert time2 < time1 / 10 # В 10 раз быстрее
10. Load Tests (Тесты нагрузки)
Цель: Проверить как система работает под нагрузкой
import locust
class UserBehavior(locust.HttpUser):
"""Симуляция поведения пользователя"""
wait_time = locust.between(1, 5)
@locust.task(1)
def list_users(self):
self.client.get("/api/v1/users/")
@locust.task(2)
def get_user(self):
self.client.get("/api/v1/users/1/")
@locust.task(1)
def create_user(self):
self.client.post("/api/v1/users/", json={
'email': 'test@example.com',
'password': 'pass123'
})
# Запуск: locust -f locustfile.py
Мой набор инструментов для тестирования
Фреймворки:
- pytest — основной фреймворк (лучше чем unittest)
- unittest/mock — встроенные мокирующие инструменты
- hypothesis — генеративное тестирование
- locust — load тестирование
Django специфично:
- pytest-django — интеграция с Django
- factory-boy — создание тестовых объектов
- freezegun — мокирование времени
- responses — мокирование HTTP запросов
Coverage:
- pytest-cov — измерение покрытия кода
- Стремлюсь к 90%+ coverage
Best Practices
# 1. Arrange-Act-Assert паттерн
def test_something():
# Arrange - подготовка
user = create_test_user()
# Act - действие
result = user.get_full_name()
# Assert - проверка
assert result == "John Doe"
# 2. Одно assert на тест (если возможно)
# 3. Тест должен быть быстрым (< 100ms)
# 4. Использовать fixtures для переиспользуемых данных
# 5. Давать понятные имена тестам
Заключение
Я умею писать:
- Unit tests для отдельных функций
- Integration tests для взаимодействия компонентов
- E2E tests для полных пользовательских сценариев
- API tests для REST endpoints
- Performance tests для оптимизации
- Load tests для масштабируемости
- Parametrized tests для тестирования с разными данными
- Property-based tests для поиска edge cases
Тестирование — это не то что приходит в конце, это часть разработки. Я пишу тесты ЧТО БЫ разработать (TDD), а не ПОСЛЕ разработки.