- Структура тестов должна соответствовать структуре приложения
- Названия тестов соответствуют 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