Написание тестов на Python с помощью pytest: от простого к сложному
Основы написания тестов на Python с pytest
Как создать простой тест и запустить его?
Для начала требуется установить библиотеку pytest:
pip install pytestA b test python (a/b тестирование в python)
Затем создается файл с тестами, имя которого начинается на test_ или заканчивается на _test.py. Внутри пишутся функции, начинающиеся на test_.
# test_example.py
def test_addition():
result = 2 + 2
assert result == 4тесты алгоритмы и программирование python (тестирование алгоритмов и программ на python)
Запуск тестов выполняется командой:
pytest
Python code tests (тестирование кода в python)
Основной элемент - assert. Если условие ложно, тест считается проваленным. Это заменяет self.assertEqual и другие методы из unittest. Для проверки исключений используется pytest.raises.
import pytest
def test_zero_division():
with pytest.raises(ZeroDivisionError):
1 / 0Py test python (написание тестов на python (pytest))
Типичная ошибка: название файла или функции не соответствует шаблону. Решение - проверить шаблон или запустить pytest с явным указанием файла: pytest test_something.py.
Ошибка импорта возникает, если не установлены зависимости или не настроен путь. Рекомендуется запускать тесты из корня проекта.
Как организовать тесты с использованием параметризации?
Параметризация позволяет запускать один тест с разными наборами данных с помощью декоратора @pytest.mark.parametrize:
import pytest
@pytest.mark.parametrize('a, b, expected', [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
])
def test_sum(a, b, expected):
assert a + b == expectedTest data python (создание тестовых данных в python)
Цель: избежать дублирования кода и улучшить покрытие.
Как переиспользовать ресурсы с помощью фикстур?
Фикстуры - функции, подготавливающие данные перед тестом. Создаются с декоратором @pytest.fixture:
import pytest
@pytest.fixture
def db_connection():
connection = connect_to_database()
yield connection
connection.close()
def test_query(db_connection):
result = db_connection.fetch('SELECT 1')
assert result == 1
У каждой фикстуры есть область видимости (scope): function, class, module, session. Для долгих ресурсов стоит указывать scope='session'.
Проблема: конфликт имен фикстур. Если две фикстуры в разных файлах называются одинаково, pytest может применить не ту. Решение - помечать фикстуры модулем или использовать уникальные имена.
Фикстуры с yield выполняют код после завершения теста. Важно не забыть закрыть ресурс в блоке after yield.
Как использовать маркеры для группировки тестов?
Маркеры позволяют метить тесты для выборочного запуска. Например, @pytest.mark.slow для медленных тестов:
import pytest
@pytest.mark.slow
def test_large_computation():
...
Запуск только медленных тестов: pytest -m slow. Маркеры регистрируют в pytest.ini для подавления предупреждений.
Как тестировать исключения и ошибки?
Используется контекст pytest.raises с возможностью проверки сообщения:
import pytest
def test_type_error():
with pytest.raises(TypeError, match='unsupported operand type'):
'2' + 2
Как организовать тестирование веб-запросов с моками?
Библиотека pytest-mock предоставляет фикстуру mocker для замены внешних вызовов:
import pytest
def test_request(mocker):
mocker.patch('requests.get', return_value=Mock(status_code=200))
response = my_app.fetch_data()
assert response.status_code == 200
Ошибка: попытка замокировать асинхронную функцию без pytest-asyncio. Для асинхронных тестов следует добавлять маркер @pytest.mark.asyncio и устанавливать библиотеку pytest-asyncio.
Цели использования перечисленных подходов:
- Параметризация сокращает повторяющийся код и расширяет набор тестовых данных.
- Фикстуры изолируют ресурсы и упрощают подготовку/чистку данных.
- Маркеры помогают разделить тесты на быстрые/медленные, интеграционные/юнит.
- Моки заменяют реальные зависимости, делая тесты быстрыми и независимыми.
Расширенные примеры тестов на pytest
Рассмотрим более сложные сценарии с полным кодом и результатами.
Пример 1: Тестирование асинхронного кода
Сначала требуется установить pytest-asyncio:
pip install pytest-asyncio
# test_async.py
import asyncio
import pytest
async def fetch_data():
await asyncio.sleep(0.1)
return 42
@pytest.mark.asyncio
async def test_async_fetch():
result = await fetch_data()
assert result == 42
Запуск:
pytest test_async.py -v
test_async.py::test_async_fetch PASSED
Проблема: если забыть маркер @pytest.mark.asyncio, тест не выполнится или вызовет ошибку. Решение - всегда указывать маркер или настроить автоматическую регистрацию в pyproject.toml.
Пример 2: Параметризация с несколькими аргументами и фикстурами
# test_param.py
import pytest
@pytest.fixture
def base_value():
return 10
@pytest.mark.parametrize('multiplier, expected', [
(1, 10),
(2, 20),
(3, 30),
])
def test_multiplication(base_value, multiplier, expected):
assert base_value * multiplier == expected
Результат:
test_param.py::test_multiplication[1-10] PASSED test_param.py::test_multiplication[2-20] PASSED test_param.py::test_multiplication[3-30] PASSED
Пример 3: Тестирование базы данных с фикстурой scope='session'
# test_db.py
import pytest
import sqlite3
@pytest.fixture(scope='session')
def db_connection():
conn = sqlite3.connect(':memory:')
conn.execute('CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)')
conn.commit()
yield conn
conn.close()
def test_insert(db_connection):
db_connection.execute('INSERT INTO test (id, value) VALUES (1, "hello")')
db_connection.commit()
cursor = db_connection.execute('SELECT value FROM test WHERE id=?')
assert cursor.fetchone()[0] == 'hello'
def test_rollback(db_connection):
# Каждый тест использует ту же сессионную БД, поэтому данные из предыдущего теста видны
cursor = db_connection.execute('SELECT COUNT(*) FROM test')
count = cursor.fetchone()[0]
assert count == 1 # из предыдущего теста
Результат:
test_db.py::test_insert PASSED test_db.py::test_rollback PASSED
Проблема: использование сессионной фикстуры может привести к влиянию тестов друг на друга. Для изоляции стоит использовать scope='function' или делать откат изменений.
Пример 4: Тестирование вызова внешнего API с mock
# test_api.py
import pytest
from unittest.mock import Mock
import requests
def fetch_user(user_id):
response = requests.get(f'https://api.example.com/users/{user_id}')
return response.json()
def test_fetch_user(mocker):
mock_response = Mock()
mock_response.json.return_value = {'id': 1, 'name': 'Alice'}
mocker.patch('requests.get', return_value=mock_response)
result = fetch_user(1)
assert result == {'id': 1, 'name': 'Alice'}
requests.get.assert_called_once_with('https://api.example.com/users/1')
Результат:
test_api.py::test_fetch_user PASSED
Пример 5: Использование плагина pytest-cov для покрытия
Установка:
pip install pytest-cov
Запуск с отчетом:
pytest --cov=my_module --cov-report=term-missing test/
Name Stmts Miss Cover Missing ------------------------------------------- my_module.py 10 2 80% 3, 7 -------------------------------------------
Пример 6: Параллельный запуск с pytest-xdist
Установка:
pip install pytest-xdist
Запуск на нескольких ядрах:
pytest -n 4
Результат аналогичен, но тесты выполняются параллельно.
Проблема: параллельные тесты не должны зависеть друг от друга (общие файлы, БД). Решение - использовать изолированные ресурсы или фикстуры с xdist_group.