Python · Тестирование с pytest · Продвинутый
Фабрики фикстур и тестовые данные
Создавайте гибкие тестовые данные с помощью фабричных фикстур и библиотеки Faker.
Краткое введение в тему и пояснения перед упражнениями (упражнения ниже):
Фабричные фикстуры на практике: пользователи, продукты и Faker
#Справочная карточка: фабричные фикстуры и Faker
#Упражнения:
Написать фабричную фикстуру make_user
#Напишите pytest-фикстуру `make_user`, возвращающую функцию-фабрику. Фабрика должна: - Принимать именованные аргументы: `username`, `email`, `role`, `age` - Использовать разумные дефолты: `username='testuser'`, `email` выводится из username если не задан, `role='user'`, `age=25` - Возвращать словарь со всеми четырьмя полями Напишите два теста: 1. `test_default_user` -- создать пользователя без аргументов, проверить `role == 'user'` и что email содержит username 2. `test_two_users` -- создать двух пользователей с разными username, проверить что они разные
# test_factory_basic.py
import pytest
@pytest.fixture
def make_user():
def _factory(username='testuser', email=None, role='user', age=25):
# если email равен None, вывести его из username
...
return ...
return _factory
def test_default_user(make_user):
user = make_user()
assert user['role'] == 'user'
assert ... # email содержит username
def test_two_users(make_user):
a = make_user(username='alice')
b = make_user(username='bob')
assert ...
Решение
# test_factory_basic.py
import pytest
@pytest.fixture
def make_user():
def _factory(username='testuser', email=None, role='user', age=25):
if email is None:
email = f'{username}@example.com'
return {'username': username, 'email': email, 'role': role, 'age': age}
return _factory
def test_default_user(make_user):
user = make_user()
assert user['role'] == 'user'
assert user['username'] in user['email']
def test_two_users(make_user):
a = make_user(username='alice')
b = make_user(username='bob')
assert a['username'] != b['username']
Добавить Faker для реалистичных генерируемых данных
#Установите `faker` и обновите `make_user` для генерации реалистичных фиктивных данных по умолчанию: - `username` из `fake.user_name()` - `email` из `fake.email()` - `first_name` и `last_name` из `fake.first_name()` / `fake.last_name()` Все поля должны по-прежнему быть переопределяемыми через именованные аргументы. Добавьте autouse фикстуру с session scope, вызывающую `Faker.seed(0)` для воспроизводимого вывода. Напишите тест `test_users_have_unique_emails`, создающий 5 пользователей без аргументов и проверяющий, что все их email различны.
# test_faker_factory.py
import pytest
from faker import Faker
@pytest.fixture(scope='session', autouse=True)
def seed_faker():
Faker.seed(0)
@pytest.fixture
def make_user():
fake = Faker()
def _factory(**kwargs):
defaults = {
'username': ...,
'email': ...,
'first_name': ...,
'last_name': ...,
}
defaults.update(kwargs)
return defaults
return _factory
def test_users_have_unique_emails(make_user):
users = [make_user() for _ in range(5)]
emails = [u['email'] for u in users]
assert len(set(emails)) == len(emails)
Решение
# test_faker_factory.py
import pytest
from faker import Faker
@pytest.fixture(scope='session', autouse=True)
def seed_faker():
Faker.seed(0)
@pytest.fixture
def make_user():
fake = Faker()
def _factory(**kwargs):
defaults = {
'username': fake.user_name(),
'email': fake.email(),
'first_name': fake.first_name(),
'last_name': fake.last_name(),
}
defaults.update(kwargs)
return defaults
return _factory
def test_users_have_unique_emails(make_user):
users = [make_user() for _ in range(5)]
emails = [u['email'] for u in users]
assert len(set(emails)) == len(emails)
Написать фабрику make_product и создать несколько продуктов
#Напишите фабричную фикстуру `make_product`. Каждый продукт должен иметь: - `name` -- строку (используйте Faker или простой дефолт вроде `'Product'`) - `slug` -- выводится из name если не задан (строчные буквы, пробелы -> дефисы) - `price` -- float, дефолт `9.99` - `in_stock` -- bool, дефолт `True` Напишите `test_three_products(make_product)`, который: - Создаёт 3 продукта с ценами `5.00`, `10.00`, `20.00` - Проверяет, что все три в наличии - Проверяет, что сумма их цен равна `35.00`
# test_products_factory.py
import pytest
@pytest.fixture
def make_product():
def _factory(name='Test Product', slug=None, price=9.99, in_stock=True):
if slug is None:
slug = name.lower().replace(' ', '-')
return {'name': name, 'slug': slug, 'price': price, 'in_stock': in_stock}
return _factory
def test_three_products(make_product):
products = [
make_product(price=...),
make_product(price=...),
make_product(price=...),
]
assert all(...)
assert sum(p['price'] for p in products) == ...
Решение
# test_products_factory.py
import pytest
@pytest.fixture
def make_product():
def _factory(name='Test Product', slug=None, price=9.99, in_stock=True):
if slug is None:
slug = name.lower().replace(' ', '-')
return {'name': name, 'slug': slug, 'price': price, 'in_stock': in_stock}
return _factory
def test_three_products(make_product):
products = [
make_product(price=5.00),
make_product(price=10.00),
make_product(price=20.00),
]
assert all(p['in_stock'] for p in products)
assert sum(p['price'] for p in products) == pytest.approx(35.00)
Написать фабрику для вложенной структуры
#Напишите фабричную фикстуру `make_order`, зависящую от `make_user` и `make_product`. Заказ должен быть словарём с: - `user` -- словарь пользователя (из `make_user()`) - `items` -- список словарей продуктов (дефолт: один продукт из `make_product()`) - `status` -- строка, дефолт `'pending'` Напишите два теста: 1. `test_order_default_has_one_item` -- создать заказ с дефолтами, проверить `len(order['items']) == 1` 2. `test_order_custom_items` -- создать два продукта явно, передать их как `items`, проверить что у заказа 2 элемента
# test_order_factory.py
import pytest
@pytest.fixture
def make_user():
def _factory(**kwargs):
defaults = {'username': 'testuser', 'email': '[email protected]', 'role': 'user'}
defaults.update(kwargs)
return defaults
return _factory
@pytest.fixture
def make_product():
def _factory(**kwargs):
defaults = {'name': 'Widget', 'price': 9.99, 'in_stock': True}
defaults.update(kwargs)
return defaults
return _factory
@pytest.fixture
def make_order(make_user, make_product):
def _factory(**kwargs):
defaults = {
'user': ...,
'items': [...],
'status': 'pending',
}
defaults.update(kwargs)
return defaults
return _factory
def test_order_default_has_one_item(make_order):
order = make_order()
assert len(order['items']) == 1
def test_order_custom_items(make_order, make_product):
p1 = make_product(name='A')
p2 = make_product(name='B')
order = make_order(items=[p1, p2])
assert len(order['items']) == 2
Решение
# test_order_factory.py
import pytest
@pytest.fixture
def make_user():
def _factory(**kwargs):
defaults = {'username': 'testuser', 'email': '[email protected]', 'role': 'user'}
defaults.update(kwargs)
return defaults
return _factory
@pytest.fixture
def make_product():
def _factory(**kwargs):
defaults = {'name': 'Widget', 'price': 9.99, 'in_stock': True}
defaults.update(kwargs)
return defaults
return _factory
@pytest.fixture
def make_order(make_user, make_product):
def _factory(**kwargs):
defaults = {
'user': make_user(),
'items': [make_product()],
'status': 'pending',
}
defaults.update(kwargs)
return defaults
return _factory
def test_order_default_has_one_item(make_order):
order = make_order()
assert len(order['items']) == 1
def test_order_custom_items(make_order, make_product):
p1 = make_product(name='A')
p2 = make_product(name='B')
order = make_order(items=[p1, p2])
assert len(order['items']) == 2
Скомбинировать фабричную фикстуру с parametrize
#Используя фабричную фикстуру `make_user`, напишите параметризованный тест, проверяющий функцию `has_access(user, resource)`. Реализуйте `has_access(user, resource)`: - Возвращает `True` если `user['role'] == 'admin'` или `resource == 'public'` - Возвращает `False` иначе Параметризуйте `test_access` этими кейсами (role, resource, expected): - `('admin', 'secret', True)` - `('user', 'public', True)` - `('user', 'secret', False)` - `('guest', 'private', False)` Используйте `make_user(role=role)` внутри тела теста для создания пользователя.
# test_access.py
import pytest
def has_access(user, resource):
...
@pytest.fixture
def make_user():
def _factory(**kwargs):
defaults = {'username': 'testuser', 'role': 'user'}
defaults.update(kwargs)
return defaults
return _factory
@pytest.mark.parametrize('role,resource,expected', [
('admin', 'secret', True),
('user', 'public', True),
('user', 'secret', False),
('guest', 'private', False),
])
def test_access(make_user, role, resource, expected):
user = make_user(role=role)
assert has_access(user, resource) is expected
Решение
# test_access.py
import pytest
def has_access(user, resource):
return user['role'] == 'admin' or resource == 'public'
@pytest.fixture
def make_user():
def _factory(**kwargs):
defaults = {'username': 'testuser', 'role': 'user'}
defaults.update(kwargs)
return defaults
return _factory
@pytest.mark.parametrize('role,resource,expected', [
('admin', 'secret', True),
('user', 'public', True),
('user', 'secret', False),
('guest', 'private', False),
])
def test_access(make_user, role, resource, expected):
user = make_user(role=role)
assert has_access(user, resource) is expected