Фундамент: pytest-asyncio и окружение
Этот урок — критический фундамент всего курса. Мы потратим 60 минут (30 из них на установку!) чтобы настроить полноценное async-окружение для тестирования.
ВАЖНО: Без правильной настройки все следующие уроки будут бессмысленны. Не пропускайте этот урок.
Prerequisite Check
Перед началом убедитесь:
# test_async_knowledge.py
import asyncio
async def check_knowledge():
"""Вы ДОЛЖНЫ понимать этот код"""
tasks = [asyncio.sleep(0.1) for _ in range(5)]
await asyncio.gather(*tasks)
lock = asyncio.Lock()
async with lock:
print("Critical section")
check_knowledge() # Если не понимаете — курс НЕ для вас!Если этот код для вас непонятен — вернитесь к изучению async/await. Без этого фундамента курс будет потерей времени.
Установка окружения (30 минут)
Шаг 1: Docker (10 минут)
macOS:
# Установите Docker Desktop
brew install --cask docker
# Запустите Docker Desktop из ApplicationsLinux (Ubuntu):
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
# Перелогиньтесь!Windows:
- Скачайте Docker Desktop с docker.com
- Включите WSL 2
Проверка:
docker --version
# Docker version 24.0.0 или вышеШаг 2: PostgreSQL (10 минут)
docker run -d \
--name pytest-postgres \
-e POSTGRES_PASSWORD=testpass \
-e POSTGRES_USER=postgres \
-e POSTGRES_DB=pytest_test \
-p 5432:5432 \
postgres:15-alpine
# Проверка
docker exec pytest-postgres pg_isready
# /var/run/postgresql:5432 - accepting connectionsСоздаём тестовую схему:
docker exec -i pytest-postgres psql -U postgres -d pytest_test <<EOF
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
EOFШаг 3: Redis (5 минут)
docker run -d \
--name pytest-redis \
-p 6379:6379 \
redis:7-alpine
# Проверка
docker exec pytest-redis redis-cli ping
# PONGШаг 4: Python зависимости (5 минут)
# Создайте виртуальное окружение
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Установите зависимости
pip install pytest pytest-asyncio asyncpg redis[asyncio]Проверка установки:
# test_deps.py
import pytest
import asyncpg
import redis.asyncio as aioredis
print("✅ All deps installed")python test_deps.py
# ✅ All deps installedpytest-asyncio: первый тест (15 минут)
Настройка pytest.ini
# pytest.ini
[pytest]
asyncio_mode = autoЧто делает asyncio_mode = auto:
- Автоматически обнаруживает async тесты
- Управляет event loop за вас
- Не нужно декорировать каждый тест
Первый async тест
# tests/test_first_async.py
import pytest
@pytest.mark.asyncio
async def test_simple_async():
"""Простейший async тест"""
import asyncio
await asyncio.sleep(0.001)
assert TrueЗапускаем:
pytest tests/test_first_async.py -vОжидаемый результат:
test_simple_async PASSED✅ Если тест прошел — pytest-asyncio работает!
Async фикстуры: базовый пример (15 минут)
Простая async фикстура
# conftest.py
import pytest
@pytest.fixture
async def async_value():
"""Async фикстура возвращает значение"""
import asyncio
await asyncio.sleep(0.001) # Имитация async операции
return 42
@pytest.mark.asyncio
async def test_async_fixture(async_value):
"""Тест использует async фикстуру"""
assert async_value == 42Фикстура с setup/teardown
# conftest.py
import pytest
@pytest.fixture
async def async_resource():
"""Async фикстура с teardown"""
# Setup
print("Opening async resource")
resource = {"status": "open"}
yield resource # Передаём в тест
# Teardown
print("Closing async resource")
resource["status"] = "closed"Тест:
@pytest.mark.asyncio
async def test_resource(async_resource):
assert async_resource["status"] == "open"
# После теста выполнится teardownПодключение к PostgreSQL (10 минут)
Async фикстура для PostgreSQL
# conftest.py
import pytest
import asyncpg
@pytest.fixture
async def db_connection():
"""Async connection к PostgreSQL"""
conn = await asyncpg.connect(
host="localhost",
port=5432,
user="postgres",
password="testpass",
database="pytest_test"
)
yield conn
await conn.close()Первый тест с БД
# tests/test_postgres.py
import pytest
@pytest.mark.asyncio
async def test_postgres_connection(db_connection):
"""Проверяем что БД доступна"""
result = await db_connection.fetchval("SELECT 1")
assert result == 1
@pytest.mark.asyncio
async def test_insert_user(db_connection):
"""Вставляем пользователя"""
await db_connection.execute(
"INSERT INTO users (email) VALUES ($1)",
"test@example.com"
)
count = await db_connection.fetchval("SELECT COUNT(*) FROM users")
assert count > 0Запускаем:
pytest tests/test_postgres.py -vПроблема: Второй запуск упадет! Почему?
asyncpg.UniqueViolationError: duplicate key valueПричина: Данные остались в БД после первого теста.
Исправление (в следующем уроке): добавим транзакции с rollback.
Подключение к Redis (10 минут)
Async фикстура для Redis
# conftest.py
import pytest
import redis.asyncio as aioredis
@pytest.fixture
async def redis_client():
"""Async Redis client"""
client = aioredis.from_url(
"redis://localhost:6379/15", # DB 15 для тестов
decode_responses=True
)
yield client
await client.flushdb() # Чистим после теста
await client.close()Первый тест с Redis
# tests/test_redis.py
import pytest
@pytest.mark.asyncio
async def test_redis_set_get(redis_client):
"""Set/Get в Redis"""
await redis_client.set("key", "value")
result = await redis_client.get("key")
assert result == "value"
@pytest.mark.asyncio
async def test_redis_isolation(redis_client):
"""Проверяем изоляцию"""
# Ключ из предыдущего теста не должен быть виден
result = await redis_client.get("key")
assert result is None # ✅ flushdb сработалТипичные ошибки окружения
Ошибка #1: Забыли pytest.ini
# ❌ БЕЗ pytest.ini
@pytest.mark.asyncio # Нужен декоратор!
async def test():
pass# ✅ С pytest.ini
[pytest]
asyncio_mode = auto
# Декоратор не нужен для async def test_*Ошибка #2: Неправильный asyncio_mode
# ❌ ПЛОХО
asyncio_mode = strict # Старый режим
# ✅ ХОРОШО
asyncio_mode = auto # СовременныйОшибка #3: Docker контейнеры не запущены
# Проверка
docker ps
# Если контейнеров нет:
docker start pytest-postgres pytest-redisОшибка #4: Порты заняты
# Проверка портов
lsof -i :5432 # PostgreSQL
lsof -i :6379 # Redis
# Если порт занят, остановите процесс или измените порт
docker run -p 5433:5432 postgres:15-alpineПроверка финального окружения
# tests/test_environment.py
import pytest
import asyncpg
import redis.asyncio as aioredis
@pytest.mark.asyncio
async def test_full_environment():
"""Проверяем что всё работает"""
# PostgreSQL
pg_conn = await asyncpg.connect(
"postgresql://postgres:testpass@localhost:5432/pytest_test"
)
pg_result = await pg_conn.fetchval("SELECT 1")
await pg_conn.close()
assert pg_result == 1
# Redis
redis = aioredis.from_url("redis://localhost:6379/15")
await redis.set("test", "ok")
redis_result = await redis.get("test")
await redis.close()
assert redis_result == "ok"
print("✅ Environment ready!")Запускаем:
pytest tests/test_environment.py -v -sОжидаемый результат:
test_full_environment PASSED
✅ Environment ready!Что вы настроили
✅ Docker с PostgreSQL и Redis ✅ pytest-asyncio с auto mode ✅ asyncpg для async PostgreSQL ✅ redis[asyncio] для async Redis ✅ Базовые async фикстуры
Следующий урок
Теперь окружение готово. В следующем уроке добавим транзакции с rollback для полной изоляции тестов.
Переходите к уроку 1: Async фикстуры для PostgreSQL
Troubleshooting
Проблема: docker: command not found
Решение: Установите Docker (см. Шаг 1)
Проблема: asyncpg.CannotConnectNowError
Решение: PostgreSQL контейнер не запущен: docker start pytest-postgres
Проблема: ModuleNotFoundError: No module named 'pytest_asyncio'
Решение: pip install pytest-asyncio
Проблема: Тесты висят и не завершаются
Решение: Проверьте pytest.ini, должно быть asyncio_mode = auto