--- description: "Паттерны тестирования, структура тестов и использование pytest" globs: ["tests/**/*.py", "test_*.py"] --- # Паттерны тестирования ## Структура тестов Тесты находятся в директории `tests/` и используют pytest: ``` tests/ ├── conftest.py # Общие фикстуры ├── conftest_*.py # Специализированные фикстуры ├── mocks.py # Моки и заглушки └── test_*.py # Тестовые файлы ``` ## Конфигурация pytest Настройки в `pyproject.toml`: - `asyncio-mode=auto` - автоматический режим для async тестов - Маркеры: `asyncio`, `slow`, `integration`, `unit` - Фильтрация предупреждений ## Структура теста ```python import pytest from database.async_db import AsyncBotDB from database.repositories.user_repository import UserRepository @pytest.mark.asyncio async def test_user_repository_add_user(db_path): """Тест добавления пользователя.""" # Arrange repo = UserRepository(db_path) user = User(user_id=123, full_name="Test User") # Act await repo.add_user(user) # Assert result = await repo.get_user_by_id(123) assert result is not None assert result.full_name == "Test User" ``` ## Фикстуры ### Общие фикстуры (conftest.py) ```python import pytest import tempfile import os @pytest.fixture def db_path(): """Создает временный файл БД для тестов.""" fd, path = tempfile.mkstemp(suffix='.db') os.close(fd) yield path os.unlink(path) @pytest.fixture async def async_db(db_path): """Создает AsyncBotDB для тестов.""" db = AsyncBotDB(db_path) await db.create_tables() yield db ``` ### Использование фикстур ```python @pytest.mark.asyncio async def test_something(async_db): # async_db уже инициализирован result = await async_db.some_method() assert result is not None ``` ## Моки Используйте `mocks.py` для общих моков: ```python from unittest.mock import AsyncMock, MagicMock def mock_bot(): """Создает мок бота.""" bot = MagicMock() bot.send_message = AsyncMock() return bot ``` ## Маркеры Используйте маркеры для категоризации тестов: ```python @pytest.mark.unit @pytest.mark.asyncio async def test_unit_test(): """Быстрый unit тест.""" ... @pytest.mark.integration @pytest.mark.asyncio async def test_integration_test(): """Медленный integration тест.""" ... @pytest.mark.slow @pytest.mark.asyncio async def test_slow_test(): """Медленный тест.""" ... ``` Запуск с фильтрацией: ```bash pytest -m "not slow" # Пропустить медленные тесты pytest -m unit # Только unit тесты ``` ## Тестирование Handlers ```python from aiogram import Bot, Dispatcher from aiogram.fsm.storage.memory import MemoryStorage from unittest.mock import AsyncMock @pytest.mark.asyncio async def test_handler(mock_bot, mock_message): """Тест handler.""" # Arrange dp = Dispatcher(storage=MemoryStorage()) # Регистрация handler ... # Act await dp.feed_update(mock_update) # Assert mock_bot.send_message.assert_called_once() ``` ## Тестирование Services ```python @pytest.mark.asyncio async def test_service_method(async_db): """Тест метода сервиса.""" service = SomeService(async_db, {}) result = await service.do_something() assert result is not None ``` ## Тестирование Repositories ```python @pytest.mark.asyncio async def test_repository_crud(db_path): """Тест CRUD операций репозитория.""" repo = SomeRepository(db_path) await repo.create_tables() # Create entity = SomeEntity(...) await repo.add(entity) # Read result = await repo.get_by_id(entity.id) assert result is not None # Update entity.field = "new_value" await repo.update(entity) # Delete await repo.delete(entity.id) result = await repo.get_by_id(entity.id) assert result is None ``` ## Best Practices 1. **Используйте фикстуры** для переиспользования setup/teardown 2. **Изолируйте тесты** - каждый тест должен быть независимым 3. **Используйте временные БД** для тестов репозиториев 4. **Мокируйте внешние зависимости** (API, файловая система) 5. **Пишите понятные имена тестов** - они должны описывать что тестируется 6. **Используйте Arrange-Act-Assert** паттерн 7. **Тестируйте граничные случаи** и ошибки