Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Группировка тестов в pytest
Организация тестов — критична для поддержки больших test suite'ов. pytest предоставляет несколько мощных инструментов для группировки, фильтрации и запуска тестов.
1. Группировка по классам (pytest-классы)
Тесты можно организовать в классы, которые логически объединяют связанную функциональность.
class TestUserAuthentication:
"""Группа тестов для аутентификации"""
def test_login_with_valid_credentials(self):
user = authenticate("john@example.com", "password123")
assert user.is_authenticated
def test_login_with_invalid_credentials(self):
with pytest.raises(AuthenticationError):
authenticate("john@example.com", "wrongpassword")
def test_login_with_nonexistent_user(self):
with pytest.raises(UserNotFoundError):
authenticate("ghost@example.com", "password")
class TestUserProfile:
"""Группа тестов для профиля пользователя"""
def test_update_profile_name(self):
user = User(name="Old Name")
user.update(name="New Name")
assert user.name == "New Name"
def test_profile_cannot_be_empty(self):
user = User()
with pytest.raises(ValidationError):
user.validate()
Преимущества:
- Логическая организация — тесты для одного функционала вместе
- Параллельный запуск — pytest может распределять классы между воркерами
- Фиксчуры уровня класса — можно использовать
setup_methodиteardown_method
2. Маркеры (marks) для категоризации
Маркеры — это tags, которые помогают фильтровать тесты при запуске.
import pytest
@pytest.mark.unit
def test_simple_calculation():
assert 2 + 2 == 4
@pytest.mark.integration
def test_database_connection():
db = connect_to_database()
assert db.is_alive()
@pytest.mark.slow
@pytest.mark.integration
def test_heavy_processing():
result = process_large_dataset()
assert len(result) > 0
@pytest.mark.skip(reason="Feature not implemented yet")
def test_future_feature():
pass
@pytest.mark.xfail(reason="Known bug in production")
def test_known_bug():
assert 1 == 2 # Ожидаемо падает
Запуск с маркерами:
# Запустить только unit-тесты
pytest -m unit
# Запустить integration, но исключить slow
pytest -m "integration and not slow"
# Запустить всё, кроме медленных
pytest -m "not slow"
Определение собственных маркеров (pytest.ini или pyproject.toml):
[pytest]
markers =
unit: Unit tests
integration: Integration tests
slow: Slow tests
smoke: Smoke tests
api: API tests
3. Структура папок
Пропер файловая структура делает поиск тестов интуитивным:
tests/
├── unit/
│ ├── domain/
│ │ ├── test_user.py
│ │ └── test_order.py
│ ├── application/
│ │ ├── test_user_service.py
│ │ └── test_order_service.py
│ └── infrastructure/
│ └── test_database.py
├── integration/
│ ├── test_api.py
│ ├── test_database.py
│ └── test_cache.py
├── e2e/
│ └── test_user_flow.py
└── conftest.py
запуск только unit-тестов:
pytest tests/unit/
pytest tests/unit/domain/
pytest tests/unit/domain/test_user.py::TestUserCreation
4. Параметризация для множественных случаев
Параметризация позволяет запустить один тест с разными входными данными.
import pytest
@pytest.mark.parametrize("input,expected", [
(2, 4),
(3, 9),
(5, 25),
])
def test_square(input, expected):
assert input ** 2 == expected
# Параметризация с несколькими параметрами
@pytest.mark.parametrize("user_type,can_delete", [
("admin", True),
("moderator", True),
("user", False),
("guest", False),
])
def test_delete_permission(user_type, can_delete):
user = create_user(user_type)
assert user.can_delete_posts() == can_delete
Результаты в консоли:
test_square[2-4] PASSED
test_square[3-9] PASSED
test_square[5-25] PASSED
5. Группировка с конфигурацией (conftest.py)
Фиксчуры позволяют группировать setup/teardown логику и переиспользовать её:
# tests/conftest.py
import pytest
from app.database import Database
@pytest.fixture(scope="session")
def db_session():
"""Фиксчура уровня сессии — создаётся один раз"""
db = Database(":memory:")
db.create_tables()
yield db
db.close()
@pytest.fixture(scope="function")
def db_transaction(db_session):
"""Фиксчура уровня теста — каждый тест получит транзакцию"""
transaction = db_session.begin_transaction()
yield transaction
transaction.rollback()
@pytest.fixture
def user(db_transaction):
"""Фиксчура-зависимость"""
user = User(name="John", email="john@example.com")
db_transaction.add(user)
return user
# tests/unit/test_user_service.py
from conftest import user
class TestUserService:
def test_get_user_by_email(self, user, db_transaction):
found = db_transaction.query(User).filter_by(email=user.email).first()
assert found.name == "John"
6. Скипание и условное выполнение
import pytest
import sys
@pytest.mark.skipif(sys.version_info < (3, 9), reason="Requires Python 3.9+")
def test_new_feature():
pass
@pytest.mark.skipif(not has_postgres(), reason="PostgreSQL not available")
def test_database():
pass
# Условный skip при запуске
def test_optional(request):
if "slow" not in request.config.getoption("-m"):
pytest.skip("Running without slow tests")
7. Запуск конкретных групп
# Запустить тесты по имени
pytest tests/ -k "test_login"
pytest tests/ -k "Authentication"
# Запустить по маркерам
pytest -m "unit or smoke"
# Запустить с verbose выводом группировок
pytest tests/ -v --tb=short
# Параллельный запуск (требует pytest-xdist)
pytest -n auto
Ключевые моменты
- Классы для логических групп — аутентификация, профиль, платежи
- Маркеры для быстрого фильтра — unit/integration/slow
- Параметризация для множественных сценариев — разные входные данные
- Фиксчуры для setup/teardown — переиспользуемая логика инициализации
- Структура папок отражает архитектуру — unit/integration/e2e
Правильная группировка тестов экономит время на разработку и облегчает поддержку проекта на всех этапах его жизни.