В наборе тестов результат зависит от порядка запуска. Объясните механизм проблемы и укажите, какое свойство тестов нарушено.
state = {"count": 0}
def test_increment():
state["count"] += 1
assert state["count"] == 1
def test_initial_state():
assert state["count"] == 0
Проблема вызвана утечкой состояния между тестами: test_increment изменяет общий объект state, а test_initial_state ожидает его исходное состояние. Нарушено свойство изоляции тестов — каждый тест должен давать один и тот же результат независимо от порядка запуска и выполнения соседних тестов.
Требование изоляции появилось как практический ответ на рост автоматизированных тестовых наборов. Когда тесты используют общие переменные, файлы, записи в базе данных или внешние сервисы, один тест начинает влиять на другой, а локализация ошибки становится сложнее.
Изоляция не означает, что каждый тест обязан запускаться в отдельном процессе. Она означает контролируемое начальное состояние и отсутствие неконтролируемых побочных эффектов, оставшихся после выполнения другого теста.
Если сначала выполнить test_increment, глобальный словарь станет равен {"count": 1}, поэтому test_initial_state завершится ошибкой. При отдельном запуске test_initial_state может проходить, создавая ложное впечатление, что тест или продукт работают корректно.
Такие проверки становятся order-dependent — зависящими от порядка запуска. Это снижает воспроизводимость, может вызывать нестабильные падения в CI и затрудняет определение виноватого изменения: падение обнаруживается в одном тесте, а причина находится в другом.
Перед каждым тестом нужно создавать или восстанавливать независимое начальное состояние. Для данного примера достаточно инициализировать state заново перед каждым тестом:
В реальном проекте это обычно реализуют через setup/teardown, фикстуры или фабрики тестовых данных. Для базы данных применяют транзакции с откатом, очистку созданных записей или отдельную схему; для файлов — временные каталоги; для внешних сервисов — контролируемые заглушки и моки.
Важно изолировать не только память процесса. Общими источниками утечки бывают кеши, переменные окружения, системное время, очереди сообщений, HTTP-моки и записи в базе данных. Сброс состояния должен происходить даже после падения теста, поэтому очистку размещают в механизме, который гарантированно выполняется после теста.
Полная изоляция повышает стоимость и время выполнения. Например, отдельная база или процесс надёжнее общей фикстуры, но требует больше ресурсов. Компромисс выбирают по типу ресурса и риску: дешёвые объекты создают заново, а дорогие общие ресурсы используют только при строгом контроле состояния и запрете зависимости тестов друг от друга.
Интеграционный набор для заказов периодически падал в CI на проверке пустой корзины. Рассматривались три варианта: случайно перемешивать тесты, очищать корзину только в проблемном тесте или создавать отдельного пользователя и очищать его данные после каждого теста.
Перемешивание помогало обнаружить зависимость, но не устраняло её. Локальная очистка была дешевле, однако не учитывала другие оставшиеся данные. Выбрали отдельного пользователя на тест и гарантированную очистку данных в teardown: это снизило риск пересечения тестов и сохранило приемлемое время выполнения.
Дополнительно в CI включили запуск тестов в разных порядках. Это не заменяет изоляцию, но помогает выявлять скрытые зависимости до их проявления на случайном наборе изменений.
Нет. Случайный порядок — диагностический приём, а не исправление. Он повышает вероятность обнаружить зависимость, но тесты по-прежнему могут влиять друг на друга. Исправление состоит в управлении состоянием и восстановлении независимого начального условия.
Нет, если объект действительно неизменяем и не зависит от внешнего состояния. Но даже такой объект может стать источником проблемы, если содержит лениво вычисляемый кеш, изменяемые вложенные структуры или зависит от текущего времени и окружения. Поэтому важно проверять не только видимость объекта, но и возможность изменения его состояния.
Если состояние уже повлияло на внешний ресурс, простого сброса локальной переменной недостаточно. Например, отправленное сообщение или вызов внешнего API нельзя надёжно отменить присваиванием исходного значения. В таких случаях используют изолированный ресурс, транзакцию, тестовый дубль или заранее определённый механизм компенсации.