Обновлены пути к библиотекам в Dockerfile для соответствия новой версии Python. Исправлены все тесты, теперь все проходят
198 lines
5.2 KiB
Markdown
198 lines
5.2 KiB
Markdown
---
|
||
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. **Тестируйте граничные случаи** и ошибки
|