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 is 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()` Всі поля мають бути перевизначуваними через аргументи-ключові слова. Додайте session-scoped `autouse`-фікстуру, що викликає `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