← Назад к вопросам

Какие тесты писал на проекте?

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, которые я применял

  1. AAA Pattern — Arrange, Act, Assert
  2. One assert per test — один логический assert на тест
  3. Descriptive names — названия говорят, что тестируется
  4. Fixtures — переиспользуемые подготовки
  5. Mocking — изоляция от зависимостей
  6. Parametrize — избежание дублирования кода
  7. Coverage > 90% — хорошее покрытие кода
  8. Fast execution — unit-тесты за миллисекунды

Заключение

Тестирование — неотъемлемая часть разработки. Хорошие тесты:

  • Ловят баги до production
  • Документируют поведение кода
  • Позволяют рефакторить без страха
  • Ускоряют разработку на длинной дистанции