- Структура тестов должна соответствовать структуре приложения
- Названия тестов соответствуют API-методам
- Один тест — один кейс
- Фикстуры
- Внимательно с данными
Структура тестов должна соответствовать структуре приложения
Прежде всего, необходимо создать правильное тестовое окружение. Помните, что тесты — это тоже код, и нам необходимо продумать, как их организовать, привести в соответствие со структурой проекта и поддерживать порядок.
Представьте, что в вашем приложении есть компоненты, такие как модули, интеграции, сущности и API. Будет чудесно, если структура тестов соответствует структуре приложения. Такой подход сильно облегчает поиск нужных тестов, добавление новых, и поддержание порядка в тестах.
Например:
``` └── project ├── tests │ ├── __init__.py │ ├── conftest.py │ ├── fixtures │ │ └── api.py │ ├── helpers.py │ └── web │ ├── __init__.py │ ├── http │ │ ├── __init__.py │ └── jsonrpc │ ├── __init__.py │ └── api_v1 │ ├── __init__.py | ├── test_increase_my_salary.py └── web ├── __init__.py … ```
В такой структуре вы всегда точно знаете, где вставить новый метод JsonRpc и как быстро найти нужные тесты.
Названия тестов соответствуют API-методам
Вместо того чтобы создавать один main-файл теста, назовите свои тестовые файлы именами соответствующих методов API. Например, если у вас есть метод JsonRpc с именем «increase_my_salary», создайте файл с именем «test_increase_my_salary» в каталоге «jsonrpc». Таким образом, сама структура объясняет, какие тесты здесь, без необходимости открывать каждый файл.
Один тест — один кейс
Каждый тест должен быть сфокусирован на одном действии, которое может быть неудачным. Лучше не пишите тесты, в которых вызывается API для создания объекта, а затем используется это API. Это проблемно, поскольку при создании объекта могут и будут возникать ошибки, что приведет к ненадежности тестов. Убедитесь, что каждый тест направлен на проверку вокруг одного вызова/действия. Это облегчает понимание, отслеживание и устранение проблем.
Пример:
async def test_salary_increase_with_valid_employee_id():
# Arrange: Prepare the necessary data or context
employee_id = ... # Replace with a valid employee ID
# Act: Perform the action you're testing
result = await jsonrpc_api_v1('increase_my_salary', id=employee_id)
# Assert: Check the expected outcome
assert ... # Add your assertion for the expected result
Фикстуры
Фикстуры — неоценимая вещь, если вы задались целью писать идеальные тесты. Они позволяют повторно использовать компоненты тестового кода без необходимости ручного импорта. Пример:
# fixtures.py
import pytest
@pytest.fixture
async def create_account() -> Account:
return await Account.create(...)
@pytest.fixture
async def create_employee(create_account) -> Employee:
return await Employee.create(account=create_account, ...)
# test_increase_my_salary.py
async def test_salary_increase_request(create_employee):
employee = create_employee
result = await jsonrpc_api_v1('increase_my_salary', id=employee.id)
assert ...
Здесь мы создали фикстуру с именем «employee» и инкапсулировали ее зависимость от другого объекта account, который также определен как фикстура. При запуске теста Pytest автоматически управляет зависимостями и создает нужные объекты.
Такой подход позволяет поддерживать чистоту, упорядоченность и читабельность тестов за счет абстрагирования логики создания объектов и связей между ними.
Внимательно с данными
Иногда в рамках одного теста могут понадобиться различные объекты с разными свойствами. Вместо того чтобы создавать множество фикстур типа «employee_1» или «employee_1«, используйте фабрики. Вы можете определить функцию внутри фикстуры для создания объектов и связей между ними.
# fixtures.py
import pytest
@pytest.fixture
def create_account_factory() -> Account:
async def create_account(**kwargs):
return await Account.create(**kwargs)
return create_account
@pytest.fixture
def create_employee_factory(create_account_factory) -> Employee:
async def create_employee(employee_kwargs: dict, account_kwargs: dict):
account = await create_account_factory(**account_kwargs)
return await Employee.create(account=account, **employee_kwargs)
return create_employee
# test_send_message.py
async def test_send_message_between_employees(create_employee_factory):
# Arrange: Create employees with specific account IDs and default employees
employee_1 = await create_employee_factory(
employee_kwargs={'id': 1},
account_kwargs={'id': 101}
)
employee_2 = await create_employee_factory()
# Act: Simulate sending a message between employees
result = await send_message(employee_1, employee_2, "Hello, World!")
# Assert: Check the expected outcome
assert result == "Message sent successfully"
Использование фабрик позволяет централизовать логику, избежать путаницы в именах фикстур типа «employee_123«, а также упростить чтение и поддержку фикстур.
Фабрики и фикстуры в документации pytest