## Проблема дублювання фікстур
Коли ваш тест-сьют розростається по кількох файлах, вам захочеться мати однакові фікстури в декількох місцях -- `sample_user`, підключення до БД, токен аутентифікації. Дублювання -- це тягар обслуговування; імпорт з іншого тестового файлу є незручним і порушує модель виявлення pytest.
`conftest.py` вирішує це чисто. Це спеціальний файл, який pytest виявляє автоматично. Будь-яка фікстура, визначена в ньому, доступна для всіх тестів у тій самій директорії та всіх піддиректоріях -- **без жодного імпорту**.
## Як pytest виявляє conftest.py
Коли pytest збирає тест за шляхом `tests/unit/test_users.py`, він завантажує кожен `conftest.py`, знайдений від кореневої директорії до директорії тестового файлу:
```
project/
├── conftest.py ← завантажується 1-м (фікстури для всього проекту)
└── tests/
├── conftest.py ← завантажується 2-м (фікстури тест-сьюту)
└── unit/
├── conftest.py ← завантажується 3-м (фікстури юніт-тестів)
└── test_users.py ← тестовий файл
```
Фікстури на кожному рівні доступні для всіх тестів нижче цього рівня. Тестовий файл ніколи не імпортує з conftest -- pytest автоматично впроваджує фікстури за іменем параметра.
## Кілька conftest-файлів і пріоритет перевизначення
Якщо одне й те саме ім'я фікстури з'являється у кількох conftest-файлах, перемагає **найближчий** -- той, що ближчий до тестового файлу. Це дозволяє визначити розумний дефолт у кореневому файлі і перевизначити його для конкретного піддерева:
```
project/
├── conftest.py # base_url = 'https://prod.example.com'
└── tests/
├── conftest.py # base_url = 'https://staging.example.com' ← перевизначає для tests/
└── local/
└── conftest.py # base_url = 'http://localhost:8000' ← перевизначає для local/
```
Тести у `tests/local/` використовують `localhost`; все інше у `tests/` -- staging.
## Scope фікстур у conftest
Scope працює так само, як і у фікстурах у файлах, але session-scoped фікстури у conftest особливо потужні -- вони виконуються один раз для всього pytest-сеансу:
```python
# conftest.py
import pytest
import json
@pytest.fixture(scope='session')
def product_catalog():
with open('tests/fixtures/products.json') as f:
return json.load(f) # завантажується один раз; спільне для всіх тестів
```
Це правильне місце для ресурсомісткого налаштування: автентифіковані HTTP-сесії, підключення до БД, завантаження великих датасетів. Виконайте один раз, поділіться результатом.
## Час життя scope
| Scope | Створюється | Знищується | Найкраще для |
|-------|------------|------------|--------------|
| `function` (за замовчуванням) | перед кожним тестом | після кожного тесту | ізольований стан на тест |
| `class` | перед першим тестом у класі | після останнього тесту у класі | спільне налаштування на рівні класу |
| `module` | перед першим тестом у файлі | після останнього тесту у файлі | ресурси на рівні файлу |
| `session` | перед першим тестом запуску | після останнього тесту запуску | ресурсомістке глобальне налаштування |
## conftest -- це не модуль для імпорту
Ніколи не пишіть `from conftest import my_fixture`. pytest автоматично обробляє виявлення та впровадження. Імпорт з conftest обходить цей механізм і створює непомітні баги.
## yield-фікстури для teardown
`yield` у фікстурі розділяє налаштування та teardown. Код після `yield` виконується, коли закінчується scope фікстури -- навіть якщо тест провалився:
```python
@pytest.fixture(scope='session')
def db_connection():
conn = open_connection()
yield conn # тут виконується код тестів; conn впроваджується
conn.close() # виконується після останнього тесту, що використовує цю фікстуру
```
**Виявлення:** pytest завантажує `conftest.py` від кореневої директорії до директорії тестового файлу. Імпорт не потрібен -- фікстури впроваджуються за іменем параметра.
**Пріоритет перевизначення:** перемагає найближчий conftest. `tests/unit/conftest.py` перевизначає `tests/conftest.py` для тестів всередині `tests/unit/`.
**Час життя scope фікстури:**
| Scope | `setup` виконується | `teardown` виконується |
|-------|---------------------|------------------------|
| `function` | перед кожним тестом | після кожного тесту |
| `class` | перед першим методом | після останнього методу |
| `module` | перед першим тестом у файлі | після останнього тесту у файлі |
| `session` | один раз за запуск `pytest` | у самому кінці |
**yield-фікстура (налаштування + teardown):**
```python
@pytest.fixture(scope='session')
def resource():
obj = setup()
yield obj # тести отримують obj
teardown(obj) # виконується, коли закінчується scope
```
**Фікстура, що залежить від іншої фікстури:**
```python
@pytest.fixture
def auth_headers(auth_token): # auth_token -- це інша фікстура
return {'Authorization': f'Token {auth_token}'}
```
**Непряма параметризація:**
```python
@pytest.fixture
def user(request):
return create_user(role=request.param)
@pytest.mark.parametrize('user', ['admin', 'viewer'], indirect=True)
def test_access(user): ...
```
**Ніколи** не пишіть `from conftest import fixture` -- pytest обробляє це автоматично.
Створіть `conftest.py` у корені проекту з фікстурою `sample_users`, що повертає список з трьох словників користувачів (кожен з ключами `'name'` і `'email'`). Створіть `test_users_a.py` з тестом, що перевіряє `len(sample_users) == 3`, та `test_users_b.py` з тестом, що перевіряє наявність ключа `'email'` у кожному словнику. Жоден тестовий файл не повинен імпортувати щось з `conftest.py`. Запустіть `pytest -v` і переконайтеся, що обидва тести проходять.
# conftest.py
import pytest
@pytest.fixture
def sample_users():
# поверніть список з 3 словників, кожен з ключами 'name' і 'email'
pass
# test_users_a.py
def test_users_count(sample_users):
# assert len(sample_users) == 3
pass
# test_users_b.py
def test_users_have_email(sample_users):
# перевірте наявність 'email' у кожному користувачі
pass
Рішення
# conftest.py
import pytest
@pytest.fixture
def sample_users():
return [
{'name': 'Alice', 'email': '[email protected]'},
{'name': 'Bob', 'email': '[email protected]'},
{'name': 'Carol', 'email': '[email protected]'},
]
# test_users_a.py
def test_users_count(sample_users):
assert len(sample_users) == 3
# test_users_b.py
def test_users_have_email(sample_users):
for user in sample_users:
assert 'email' in user
02
Session-scoped фікстура, що завантажує файл один раз
Створіть `settings.json` з вмістом `{"debug": true, "page_size": 10}`. Створіть `conftest.py` з `session`-scoped фікстурою `config`, яка читає цей файл і виводить `"Loading config"` перед поверненням словника. Напишіть дві тест-функції у двох окремих файлах -- обидві використовують `config`. Запустіть `pytest -v -s` і переконайтеся, що `"Loading config"` з'являється у виводі рівно один раз.
Створіть кореневий `conftest.py` з фікстурою `base_url`, що повертає `'https://apilearn.tukas.dev'`. Створіть піддиректорію `local/` з власним `conftest.py`, що перевизначає `base_url` як `'http://localhost:8000'`. Напишіть `test_root.py` у корені та `local/test_local.py` у піддиректорії -- кожен повинен виводити `base_url` з `-s`. Запустіть `pytest -v -s` і переконайтеся, що в кожному тесті відображається правильний URL.
Створіть `conftest.py` з фікстурою `scaled`, яка читає `request.param` (число) і повертає `request.param * 10`. Напишіть параметризований тест з `indirect=True` та параметрами `[2, 5, 7]`. Кожен запуск тесту повинен отримувати `20`, `50` і `70` відповідно -- перевірте це. Запустіть `pytest -v` і переконайтеся, що у виводі з'являються три окремі тест-кейси.
# conftest.py
import pytest
@pytest.fixture
def scaled(request):
# поверніть request.param, помножений на 10
pass
# test_scale.py
import pytest
@pytest.mark.parametrize('scaled', [2, 5, 7], indirect=True)
def test_scaled_value(scaled):
# перевірте, що scaled дорівнює 20, 50 або 70 залежно від переданого param
pass
Створіть `conftest.py` з фікстурою `temp_file`, яка: (1) створює `Path('test_output.txt')` і записує до нього `'hello from fixture'`, виводячи `'SETUP'`; потім (2) передає шлях через `yield`; потім (3) видаляє файл і виводить `'TEARDOWN'`. Напишіть тест, що читає файл і перевіряє його вміст. Запустіть `pytest -v -s`, щоб побачити SETUP і TEARDOWN навколо тесту.
# conftest.py
import pytest
from pathlib import Path
@pytest.fixture
def temp_file():
path = Path('test_output.txt')
# запишіть 'hello from fixture' до path, виведіть 'SETUP'
# yield path
# видаліть файл, виведіть 'TEARDOWN'
pass
# test_tempfile.py
def test_reads_fixture_file(temp_file):
# прочитайте temp_file і перевірте вміст == 'hello from fixture'
pass
Ми використовуємо необхідні cookies для роботи сайту. З вашого дозволу ми також можемо зберігати налаштування сайту та використовувати аналітичні й рекламні cookies, щоб розуміти використання сайту й підтримувати розвиток проєкту.
* Ви завжди можете змінити свій вибір у налаштуваннях сайту.
Оберіть категорії cookies
Налаштування аналітики
Можна вимкнути аналітику використання платформи. Також можна надіслати в Google Analytics запит на видалення даних про використання цього сайту, пов'язаних із цим браузером.
Навчальний workspace
Навчайтеся, читаючи, запускаючи код і розв'язуючи задачі.
Практикуйте програмування з поясненнями тем, вправами, інструментами browser IDE, перевіркою regex і тренуванням друку коду в одному workspace.
Відкривайте інструменти у вкладках.Вправи, IDE-інструменти й тренажери залишаються доступними як вкладки сайту.
Перемикайтеся без втрати контексту.Переходьте між поясненнями, кодом та утилітами, зберігаючи своє місце.
Використовуйте sidebar як карту.Ліві панелі містять навігацію, налаштування, файли, libraries та керування інструментами.
PythonJavaScriptSQLite
Одна IDE, три практичні режими
Python у браузері.Запускайте невеликі скрипти, пробуйте бібліотеки й тренуйте API-запити без встановлення.
JavaScript для швидких експериментів.Перевіряйте код для браузера й порівнюйте ідеї поруч із навчальними матеріалами.
SQLite для практики з даними.Відкривайте оглядач бази даних, переглядайте таблиці, пишіть запити й вивчайте SQL локально.
ТемаIDE
Працюйте поруч у split tabs
Тримайте інструкції перед очима.Відкрийте вправу або довідкову сторінку поруч з IDE, замість постійних перемикань.
Порівнюйте інструменти під час навчання.Розміщуйте перевірки regex, пояснення та експерименти з кодом поруч, коли це потрібно для задачі.
Закрийте split, коли завершите.Workspace повернеться до однієї сфокусованої вкладки, а відкриті вкладки сайту залишаться доступними.
Тренажер друку коду
Або звичайного тексту
Тренажер розрахований на фізичну клавіатуру.Відкрийте цей розділ на ноутбуці або комп'ютері з широким екраном. На телефоні тренування друку не працюватиме коректно.
Швидкість: 0 зн/хв
0 слів/хв
Найкраща швидкість (60с): 0 зн/хв
0 слів/хв
Помилки: 0
Загальний час: 0.0 с
Щоб тренувати сліпий друк, не підглядайте на фізичну клавіатуру.