Тест зависит от окружения машины: проверяем настройки через monkeypatch

Функция конфигурации читает адрес сервиса из окружения. На машине разработчика тест проходит, потому что адрес давно записан в профиле оболочки. В чистом процессе значение отсутствует, и тот же код падает. Обратная ситуация не лучше: случайный внешний адрес отправляет тестовую операцию в настоящий сервис.

В тесте нужно явно задавать состояние окружения, от которого зависит сценарий. Fixture monkeypatch умеет устанавливать и удалять переменные, восстанавливая прежнее состояние после теста. Нам не приходится сохранять старое значение вручную и надеяться, что восстановление выполнится после assert.

Окружение задаёт сам тест

Сама функция должна читать настройку при вызове. Если присвоить SERVICE_URL = os.environ[...] при импорте, подмена после импорта уже не изменит сохранённую строку. Тогда тест начинает управлять порядком загрузки модулей вместо поведения функции. Удобнее загрузить настройки явно в точке входа и передать готовый объект компонентам.

import os
import pytest

def service_url() -> str:
    return os.environ["SERVICE_URL"]

def test_explicit_url(monkeypatch):
    monkeypatch.setenv("SERVICE_URL", "https://example.invalid")
    assert service_url() == "https://example.invalid"

def test_required_url(monkeypatch):
    monkeypatch.delenv("SERVICE_URL", raising=False)
    with pytest.raises(KeyError, match="SERVICE_URL"):
        service_url()

Отсутствие и пустое значение

Установите pytest командой python -m pip install pytest, сохраните файл как test_config.py и запустите python -m pytest -q test_config.py. Два теста должны пройти при любом исходном значении SERVICE_URL. Первый задаёт конкретный адрес, второй удаляет значение даже тогда, когда оно уже отсутствует, благодаря raising=False.

Не проверяйте отсутствие переменной, присваивая пустую строку, если программа различает пустое и отсутствующее значение. Это разные состояния окружения. При необходимости добавьте третий тест: функция должна либо принять пустое значение, либо отклонить его явной проверкой. В примере обязательная переменная читается через [], поэтому отсутствие означает KeyError, а пустая строка пока допустима.

Стоит проверить и время чтения. Вызовите функцию дважды, изменив переменную между вызовами, если ожидаете динамическую настройку. Если приложение намеренно читает конфигурацию один раз при запуске, тестируйте создание объекта настроек и отдельно передачу этого объекта сервису. Такое решение делает поведение понятнее, чем неявный глобальный кэш.

monkeypatch меняет состояние текущего процесса. Поэтому не запускайте параллельно две операции, которые должны видеть разные значения одной переменной, внутри одного такого теста. Для логики обработки лучше передавать настройки аргументом, а работу с окружением проверять несколькими короткими тестами адаптера. Тогда зависимость от машины остаётся в одном месте и перестаёт путешествовать по всему набору тестов.

Следующий шаг — перенести этот приём из отдельного файла в поддерживаемое приложение. В курсе «Python для продвинутых: ООП, типизация и тестирование» структура пакетов, объектная модель, mypy и pytest соединяются в типизированном трекере задач с хранением данных и логированием. Можно отработать связь между интерфейсом, реализацией и регрессионным тестом, а затем применить её к своему проекту.

Посмотреть программу и практику курса.

Документация: официальный источник по теме.

Другие задачи по теме: Типы, тесты и поведение программы.

Продолжить на практике

  • Python для продвинутых: ООП, типизация и тестирование ↗

    Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.