pytest.parametrize: почему один тест меняет данные другого
Два параметризованных сценария используют один список заданий. Первый удаляет обработанный элемент, второй неожиданно получает пустой вход. Самостоятельный запуск второго теста проходит, общий запуск падает: данные пережили границу сценария.
Параметр передаётся как объект
pytest передаёт значения параметров без автоматического копирования. Если два набора параметров содержат ссылку на один список, они обращаются к общей памяти. Изменение через pop, sort или вложенный словарь останется доступным следующему тесту. Зависимость от порядка — следствие общего объекта, а не особенность планировщика тестов.
Сначала определите договорённость функции. Обработчик очереди вправе извлекать элементы из переданного списка; тогда тест должен выдавать ему новый список. Если же функция обещает сохранять вход, вместо защитной копии закрепите это свойство отдельным assert. Копирование в тесте не должно скрывать нарушение API.
Хранить описание входа отдельно
В примере параметр содержит неизменяемый кортеж номеров. Каждый вызов теста строит из него новый список. Два одинаковых набора здесь намеренны: они проверяют повторяемость для одинакового описания. Имена ids позволяют отличить независимые запуски в отчёте.
Для вложенных объектов list(tuple_value) недостаточно, если кортеж хранит изменяемые словари. В таком случае удобнее фабрика, которая создаёт всю структуру заново. deepcopy тоже подходит не для любого объекта: открытый файл или соединение не становятся независимыми ресурсами от попытки глубокого копирования.
import pytest
def drain(queue):
result = []
while queue:
result.append(queue.pop(0) * 10)
return result
@pytest.mark.parametrize("source,expected", [
((1, 2), [10, 20]),
((1, 2), [10, 20]),
((), []),
], ids=["first-run", "repeat-same-input", "empty"])
def test_drain_has_fresh_input(source, expected):
queue = list(source)
assert drain(queue) == expected
assert queue == []
assert source in ((1, 2), ())Поймать проблему до расширения набора
При подозрении на общее состояние запустите каждый узел отдельно, затем вместе. Посмотрите, где создаётся объект: на уровне модуля, в session-фикстуре или внутри теста. Исправление должно поставить границу жизни там, где нужна независимость. Увеличение числа случайных примеров эту границу не создаёт.
Ожидаемые значения также лучше не изменять в проверяемом коде. Здесь список expected только читается. Если функция принимает и изменяет ожидаемую структуру, тест может начать сравнивать два уже испорченных объекта. Независимый расчёт ожидания важнее количества строк в таблице параметров.
Практика с понятными границами
Начните с открытого урока первых тестов pytest: обычный вход, пустой вход, отказ. Затем превратите их в параметры и проверьте независимость данных. Откройте программу, чтобы продолжить параметризацией и областями видимости фикстур.
Продолжить на практике
- Python для продвинутых: ООП, типизация и тестирование ↗
Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.
