← Назад к вопросам
Какие тесты писал на проекте?
1.0 Junior🔥 161 комментариев
#Soft Skills#Тестирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Примеры тестов, которые я писал на проектах
В своей практике я писал различные типы тестов, следуя лучшим практикам TDD и применяя пирамиду тестирования. Вот конкретные примеры из реальных проектов.
1. Unit-тесты для бизнес-логики
Расчёт скидок в e-commerce
# src/domain/pricing.py
class DiscountCalculator:
def calculate_discount(self, price: float, category: str, is_member: bool) -> float:
base_discount = self.get_category_discount(category)
if is_member:
base_discount += 0.1 # +10% для участников
return price * (1 - base_discount)
# tests/unit/domain/test_discount_calculator.py
import pytest
from src.domain.pricing import DiscountCalculator
class TestDiscountCalculator:
@pytest.fixture
def calculator(self):
return DiscountCalculator()
def test_member_gets_extra_discount(self, calculator):
"""Участники получают 10% бонусную скидку"""
# Arrange
price = 100.0
category = "electronics"
# Act
discount_member = calculator.calculate_discount(price, category, is_member=True)
discount_non_member = calculator.calculate_discount(price, category, is_member=False)
# Assert
assert discount_member < discount_non_member
assert discount_member == pytest.approx(discount_non_member * 0.9)
def test_zero_price_returns_zero(self, calculator):
"""Нулевая цена даёт нулевой результат"""
result = calculator.calculate_discount(0, "any", True)
assert result == 0
def test_invalid_category_raises_error(self, calculator):
"""Неправильная категория вызывает исключение"""
with pytest.raises(ValueError, match="Unknown category"):
calculator.calculate_discount(100, "invalid", False)
2. Unit-тесты с мокированием зависимостей
Сервис создания заказа
# src/application/order_service.py
class OrderService:
def __init__(self, db: Database, email_client: EmailClient, payment_gateway: PaymentGateway):
self.db = db
self.email_client = email_client
self.payment_gateway = payment_gateway
def create_order(self, user_id: int, items: list) -> Order:
total = sum(item.price for item in items)
# Процессируем платёж
payment_result = self.payment_gateway.charge(user_id, total)
if not payment_result.success:
raise PaymentError("Payment failed")
# Сохраняем в БД
order = Order(user_id=user_id, items=items, total=total)
self.db.save(order)
# Отправляем письмо
self.email_client.send_confirmation(user_id, order)
return order
# tests/unit/application/test_order_service.py
from unittest.mock import Mock, patch
import pytest
from src.application.order_service import OrderService
from src.domain.models import Order
class TestOrderService:
@pytest.fixture
def mocks(self):
return {
'db': Mock(),
'email_client': Mock(),
'payment_gateway': Mock()
}
@pytest.fixture
def service(self, mocks):
return OrderService(
db=mocks['db'],
email_client=mocks['email_client'],
payment_gateway=mocks['payment_gateway']
)
def test_successful_order_creation(self, service, mocks):
"""Успешное создание заказа"""
# Arrange
user_id = 123
items = [Mock(price=100), Mock(price=50)]
mocks['payment_gateway'].charge.return_value = Mock(success=True)
# Act
order = service.create_order(user_id, items)
# Assert
assert order.user_id == user_id
assert order.total == 150
mocks['payment_gateway'].charge.assert_called_once_with(user_id, 150)
mocks['db'].save.assert_called_once()
mocks['email_client'].send_confirmation.assert_called_once()
def test_payment_failure_does_not_save_order(self, service, mocks):
"""Неудачный платёж не создаёт заказ"""
# Arrange
mocks['payment_gateway'].charge.return_value = Mock(success=False)
# Act & Assert
with pytest.raises(PaymentError):
service.create_order(123, [Mock(price=100)])
# Убедиться, что БД не была обновлена
mocks['db'].save.assert_not_called()
mocks['email_client'].send_confirmation.assert_not_called()
3. Integration-тесты с БД
Тесты репозитория
# src/infrastructure/repositories/user_repository.py
from sqlalchemy.orm import Session
from src.domain.models import User
class UserRepository:
def __init__(self, db: Session):
self.db = db
def create(self, email: str, name: str) -> User:
user = User(email=email, name=name)
self.db.add(user)
self.db.commit()
return user
def find_by_email(self, email: str) -> User | None:
return self.db.query(User).filter_by(email=email).first()
def update(self, user_id: int, **kwargs) -> User:
user = self.db.query(User).get(user_id)
for key, value in kwargs.items():
setattr(user, key, value)
self.db.commit()
return user
# tests/integration/test_user_repository.py
import pytest
from src.infrastructure.repositories.user_repository import UserRepository
@pytest.fixture
def db_session():
"""Фикстура для тестовой БД"""
session = create_test_session()
yield session
session.rollback()
session.close()
class TestUserRepository:
def test_create_user(self, db_session):
"""Создание пользователя в БД"""
# Arrange
repo = UserRepository(db_session)
# Act
user = repo.create(email="test@example.com", name="John")
# Assert
assert user.id is not None
assert user.email == "test@example.com"
# Проверить, что сохранилось в БД
retrieved = db_session.query(User).get(user.id)
assert retrieved.name == "John"
def test_find_by_email_returns_none_if_not_found(self, db_session):
"""Поиск несуществующего пользователя"""
repo = UserRepository(db_session)
result = repo.find_by_email("nonexistent@example.com")
assert result is None
def test_find_by_email_returns_user(self, db_session):
"""Поиск существующего пользователя"""
repo = UserRepository(db_session)
created_user = repo.create(email="test@example.com", name="John")
found_user = repo.find_by_email("test@example.com")
assert found_user.id == created_user.id
assert found_user.email == "test@example.com"
def test_update_user(self, db_session):
"""Обновление пользователя"""
repo = UserRepository(db_session)
user = repo.create(email="test@example.com", name="John")
updated = repo.update(user.id, name="Jane")
assert updated.name == "Jane"
# Проверить в БД
retrieved = db_session.query(User).get(user.id)
assert retrieved.name == "Jane"
4. API тесты (FastAPI)
Тесты эндпоинтов
# src/presentation/api/routes/users.py
from fastapi import APIRouter, Depends
from src.application.user_service import UserService
router = APIRouter(prefix="/users")
@router.post("")
async def create_user(email: str, name: str, service: UserService = Depends()):
user = service.create_user(email, name)
return {"id": user.id, "email": user.email, "name": user.name}
@router.get("/{user_id}")
async def get_user(user_id: int, service: UserService = Depends()):
user = service.get_user(user_id)
return {"id": user.id, "email": user.email, "name": user.name}
# tests/integration/test_api_users.py
import pytest
from fastapi.testclient import TestClient
from src.main import app
@pytest.fixture
def client():
return TestClient(app)
class TestUserAPI:
def test_create_user(self, client):
"""POST /users создаёт пользователя"""
response = client.post(
"/users",
params={"email": "test@example.com", "name": "John"}
)
assert response.status_code == 200
data = response.json()
assert data["email"] == "test@example.com"
assert data["id"] is not None
def test_get_user(self, client):
"""GET /users/{id} возвращает пользователя"""
# Сначала создаём пользователя
create_response = client.post(
"/users",
params={"email": "test@example.com", "name": "John"}
)
user_id = create_response.json()["id"]
# Затем получаем его
get_response = client.get(f"/users/{user_id}")
assert get_response.status_code == 200
assert get_response.json()["email"] == "test@example.com"
def test_get_nonexistent_user_returns_404(self, client):
"""GET /users/999 возвращает 404"""
response = client.get("/users/999")
assert response.status_code == 404
5. Тесты Telegram бота (aiogram)
Тесты хэндлеров
# src/presentation/bot/handlers/user_handlers.py
from aiogram import Router, types
from aiogram.filters import Command
from src.application.user_service import UserService
router = Router()
@router.message(Command("start"))
async def cmd_start(message: types.Message, user_service: UserService):
user = await user_service.get_or_create_user(
user_id=message.from_user.id,
username=message.from_user.username
)
await message.answer(f"Привет, {user.username}!")
# tests/integration/bot/test_user_handlers.py
import pytest
from aiogram.types import Message, User, Chat
from unittest.mock import AsyncMock, patch
from src.presentation.bot.handlers.user_handlers import router
from src.domain.models import User as DomainUser
@pytest.mark.asyncio
async def test_start_command_greets_user():
"""Команда /start приветствует пользователя"""
# Arrange
message = Message(
message_id=1,
date=0,
chat=Chat(id=123, type="private"),
from_user=User(id=123, is_bot=False, first_name="John")
)
user_service = AsyncMock()
user_service.get_or_create_user.return_value = DomainUser(
id=1,
telegram_id=123,
username="john"
)
# Act
await cmd_start(message, user_service=user_service)
# Assert
user_service.get_or_create_user.assert_called_once_with(
user_id=123,
username="John"
)
# Проверяем, что ответ был отправлен
assert message.answer.called
6. Performance-тесты
Тесты производительности
# tests/performance/test_search_performance.py
import pytest
import time
from src.infrastructure.search_index import SearchIndex
class TestSearchPerformance:
@pytest.fixture
def search_index(self):
index = SearchIndex()
# Индексируем 1 млн документов
for i in range(1_000_000):
index.add_document(f"doc_{i}", f"content_{i}")
return index
def test_search_under_100ms(self, search_index):
"""Поиск должен выполняться за < 100ms"""
# Arrange
query = "content_500000"
# Act
start = time.time()
results = search_index.search(query)
elapsed = time.time() - start
# Assert
assert len(results) > 0
assert elapsed < 0.1, f"Search took {elapsed}s, expected < 0.1s"
7. Тесты с VCR.py (HTTP запросы)
Тесты с записью HTTP
# tests/integration/test_external_api.py
import pytest
import vcr
from src.services.weather_service import WeatherService
@pytest.fixture
def weather_service():
return WeatherService(api_key="test_key")
@vcr.use_cassette('tests/cassettes/weather_api.yaml')
def test_get_weather(weather_service):
"""Получение погоды из внешнего API"""
# Act
weather = weather_service.get_weather("Moscow")
# Assert
assert weather.temperature is not None
assert weather.description is not None
При первом запуске VCR записывает HTTP запрос в YAML файл. При последующих запусках используется записанный ответ, не делая реального запроса.
8. Тесты параметризованные (Parametrize)
Тесты с разными входными данными
# tests/unit/test_validators.py
import pytest
from src.domain.validators import EmailValidator
class TestEmailValidator:
@pytest.mark.parametrize("email,is_valid", [
("test@example.com", True),
("user.name@example.co.uk", True),
("invalid.email@", False),
("@example.com", False),
("no-at-sign.com", False),
("", False),
])
def test_email_validation(self, email, is_valid):
"""Проверка валидации email адресов"""
validator = EmailValidator()
assert validator.is_valid(email) == is_valid
Покрытие тестами
# Команды для запуска тестов
pytest --cov=src --cov-report=html # Генерирует отчёт
cov report: 94% # Хорошее покрытие
# Запуск только быстрых unit-тестов
pytest tests/unit -v
# Запуск только интеграционных тестов
pytest tests/integration -v
# Запуск с профилированием
pytest --profile
Best Practices, которые я применял
- AAA Pattern — Arrange, Act, Assert
- One assert per test — один логический assert на тест
- Descriptive names — названия говорят, что тестируется
- Fixtures — переиспользуемые подготовки
- Mocking — изоляция от зависимостей
- Parametrize — избежание дублирования кода
- Coverage > 90% — хорошее покрытие кода
- Fast execution — unit-тесты за миллисекунды
Заключение
Тестирование — неотъемлемая часть разработки. Хорошие тесты:
- Ловят баги до production
- Документируют поведение кода
- Позволяют рефакторить без страха
- Ускоряют разработку на длинной дистанции