Pytest fixtures простими словами для реальних проєктів
19.04.2026Коли тестів стає багато, одна й та сама підготовка даних починає повторюватися в різних місцях. Спочатку це виглядає нормально: створили користувача, підготували тимчасову директорію, зібрали фейковий конфіг і перевірили результат. Але з часом код тестів розростається, і підтримувати його стає важче. Саме тут у пригоді стають fixtures у Pytest.
Якщо пояснити просто, fixture — це спосіб один раз описати підготовку для тесту і потім використовувати її там, де потрібно. Це може бути об’єкт, функція, тестова база даних, мок, тимчасовий файл або будь-який інший ресурс, який потрібен для перевірки поведінки коду.
Що таке fixture у Pytest
Fixture — це функція, яку Pytest запускає перед тестом або групою тестів. Вона може повертати готові дані, створювати об’єкти, налаштовувати середовище або виконувати підготовчі дії. Основна ідея проста: тест отримує все необхідне через параметри, а не створює це самостійно.
Ось базовий приклад:
@pytest.fixture
def user():
return {"name": "Oleh", "role": "admin"}
def test_user_role(user):
assert user["role"] == "admin"
У цьому прикладі функція user — це fixture. Pytest бачить, що тесту потрібен аргумент з такою назвою, викликає fixture і передає результат у тест.
Чому fixtures зручні у реальних проєктах
У невеликому прикладі можна обійтися й без них. Але в реальному проєкті fixtures дають кілька важливих переваг:
- зменшують дублювання коду;
- роблять тести коротшими та зрозумілішими;
- допомагають централізувати підготовку даних;
- спрощують повторне використання однакових налаштувань;
- полегшують підтримку, коли структура проєкту змінюється.
Наприклад, якщо у вас 20 тестів для сервісу реєстрації, і в кожному потрібно створювати однакового тестового користувача, краще винести це в fixture. Тоді при зміні структури користувача ви оновите тільки одне місце.
Простий приклад для API-проєкту
Уявімо, що ви тестуєте сервіс авторизації. Часто потрібно мати тестового користувача і токен. Без fixtures це виглядає приблизно так:
def test_login():
user = {"email": "test@example.com", "password": "secret"}
token = login(user)
assert token is not None
def test_profile():
user = {"email": "test@example.com", "password": "secret"}
token = login(user)
profile = get_profile(token)
assert profile["email"] == "test@example.com"
Тут підготовка даних повторюється. З fixture код стає чистішим:
@pytest.fixture
def test_user():
return {"email": "test@example.com", "password": "secret"}
@pytest.fixture
def auth_token(test_user):
return login(test_user)
def test_login(test_user):
token = login(test_user)
assert token is not None
def test_profile(auth_token):
profile = get_profile(auth_token)
assert profile["email"] == "test@example.com"
Тепер логіка підготовки зосереджена в одному місці. Якщо формат логіну зміниться, достатньо змінити fixture.
Як працює залежність між fixtures
Одна fixture може використовувати іншу. Це дуже зручно, коли підготовка має кілька кроків. Наприклад, спочатку створюємо користувача, потім отримуємо токен, а вже потім використовуємо його в тестах.
@pytest.fixture
def api_client():
return ApiClient(base_url="https://example.com")
@pytest.fixture
def user(api_client):
return api_client.create_user(email="test@example.com")
@pytest.fixture
def token(api_client, user):
return api_client.login(user["email"], "secret")
У такій структурі кожна fixture виконує свою маленьку задачу. Це робить код модульним і зручним для читання.
Scope: коли fixture створюється і коли знищується
За замовчуванням fixture у Pytest створюється для кожного тесту окремо. Це безпечно і передбачувано, але іноді неефективно. Якщо ресурс дорогий у створенні, можна змінити область видимості через scope.
Основні варіанти такі:
function— для кожного тесту, це значення за замовчуванням;class— один раз для всіх тестів класу;module— один раз для всього модуля;session— один раз на всю тестову сесію.
Наприклад, якщо ви запускаєте тестовий контейнер бази даних, немає сенсу створювати його перед кожним тестом. У такому разі краще використати scope session або module.
@pytest.fixture(scope="session")
def db_connection():
return connect_test_db()
Yield-fixtures: підготовка і очищення
Часто недостатньо лише створити дані. Після тесту потрібно щось прибрати: закрити з’єднання, видалити файл, очистити тимчасовий об’єкт. Для цього зручно використовувати yield.
@pytest.fixture
def temp_file():
file_path = create_temp_file()
yield file_path
delete_temp_file(file_path)
Усе, що стоїть до yield, виконується перед тестом. Усе після — після завершення тесту. Це дуже корисно для ресурсів, які не можна залишати відкритими.
Поширені fixtures Pytest, які варто знати
Pytest має вбудовані fixtures, які часто рятують у повсякденній роботі. Наприклад:
tmp_path— тимчасова директорія для тестів із файлами;capsys— захоплення виводу в консоль;monkeypatch— тимчасова заміна змінних, функцій або середовища;pytest.raises— перевірка винятків, хоч це не fixture, але часто використовується поруч.
Наприклад, якщо треба протестувати роботу з файлом, tmp_path значно спрощує тест:
def test_write_file(tmp_path):
file = tmp_path / "data.txt"
file.write_text("hello")
assert file.read_text() == "hello"
Коли fixtures краще не ускладнювати
Fixtures дуже корисні, але ними також можна перегнути. Якщо зробити занадто багато прихованої логіки, тести стануть важче зрозумілими. Хороше правило: fixture має допомагати, а не приховувати поведінку.
Уникайте ситуацій, коли одна fixture запускає три інші, а ще кілька змінюють глобальний стан. Для простих випадків краще залишати прості fixtures з очевидною назвою. Якщо тесту для розуміння потрібен лише один об’єкт, не варто створювати цілу ієрархію підготовки.
Практичний підхід до структури fixtures
У реальному проєкті зручно групувати fixtures за призначенням. Наприклад:
- fixtures для даних користувача;
- fixtures для API-клієнта;
- fixtures для файлів і директорій;
- fixtures для роботи з базою даних;
- fixtures для моків і патчів.
Також часто їх виносять у окремий файл conftest.py. Це зручно, бо Pytest автоматично підхоплює fixtures звідти для тестів у відповідній папці. У результаті код тестів стає компактним, а спільна підготовка — централізованою.
Висновок
Fixtures у Pytest — це не складна магія, а просто зручний спосіб підготувати все необхідне для тесту. Вони допомагають прибрати повторення, зробити тести читабельнішими і легшими в підтримці. Якщо у вашому Python-проєкті тести починають дублювати один одного, це хороший сигнал винести спільну підготовку у fixtures.
Почніть із малого: винесіть один повторюваний об’єкт у fixture, потім додайте залежності, а згодом спробуйте yield-fixtures для очищення ресурсів. Зазвичай після кількох таких кроків структура тестів стає значно акуратнішою і зрозумілішою.
