Фикстуры Pytest
Светильники предоставляют определенный надежный контекст для тестов. Пользовательские светильники — это функции Python, украшенные @pytest.fixture. Они используются для упорядочивания предварительных условий, таких как тестовые данные, переменные среды, подключения к базам данных или файлы, необходимые для выполнения теста. Фикстуры также могут определять этапы удаления, которые очищают ресурсы после завершения теста или области действия фикстуры.
Тест запрашивает светильник, объявив аргумент с таким же именем, как и у светильника. Pytest находит светильник, запускает его и передает возвращенное или возвращенное значение в тест. Дополнительные сведения о жизненном цикле светильника см. в документации pytest для светильников.
Фикстуры наиболее полезны, когда используются повторно настройки или очистка, сложные или более понятные при именовании. Для простых одноразовых значений размещение кода настройки непосредственно в тесте может упростить его восприятие.
Ключевыми характеристиками фикстур pytest являются следующие:
-
Управление областью действия: фикстуры могут использовать параметр
scopeдля управления тем, как часто pytest создает и уничтожает значение фикстуры. Более широкие области могут повысить производительность, но они также дольше делятся состоянием. -
Настройка и демонтаж: фикстуры могут использоваться
yieldдля очистки. Расширенные фикстуры также могут получать встроеннуюrequestфикстуру (представлена позже в этом блоке) для проверки запрашивающего теста и вызоваrequest.addfinalizer()для регистрации функций обратного вызова очистки. - Внедрение зависимостей: тесты и фикстуры запрашивают фикстуры в качестве аргументов, делая зависимости явными.
- Повторное использование и модульность. Исправления можно определить один раз и повторно использовать в тестовых функциях, модулях, каталогах или проектах.
-
Автоматическая активация: фикстура, определенная с помощью
@pytest.fixture(autouse=True), выполняется для каждого теста в своем охвате доступности без необходимости перечисления в качестве аргумента. Используйте эту опцию умеренно, так как она скрывает тестовые зависимости.
Создание временного файл-фикстуры
При написании тестов, взаимодействующих с файлами, обычно требуются изолированные пути, которые не конфликтуют с другими тестами или зависят от жестко закодированного расположения. Pytest предоставляет встроенные временные исправления каталогов, которые обычно лучше всего подходят для новых тестов, так как pytest создает уникальные каталоги и управляет их хранением. Однако создание небольшого пользовательского светильника является полезным способом понять запросы и возвращаемые значения.
Следующая фикстура возвращает фабричную функцию. Само средство не создает файл во время установки. Вместо этого функция create(), являющаяся вложенной, создает файл, когда тест вызывает tmp_file():
from pathlib import Path
import tempfile
import pytest
@pytest.fixture
def tmp_file():
def create(contents=""):
temp = tempfile.NamedTemporaryFile(delete=False)
try:
path = Path(temp.name)
finally:
temp.close()
path.write_text(contents, encoding="utf-8")
return path
return create
Тестовый набор использует функцию tempfile.NamedTemporaryFile() Python и delete=False, чтобы файл оставался доступным даже после его закрытия. Дескриптор файла закрывается перед тем, как тест получает путь, что важно, если тесты должны повторно открыть или удалить файл, особенно на Windows.
Ниже приведен пример того, как тест может использовать фикстуру:
def test_file(tmp_file):
path = tmp_file("ready")
assert path.read_text(encoding="utf-8") == "ready"
В этой настройке tmp_file — это имя фикстуры, а тест получает create() функцию, возвращаемую фикстурой. Вызов tmp_file() внутри теста создает файл, записывает запрошенное содержимое и возвращает pathlib.Path объект для него.
Note
Будьте осторожны при сочетании фикстур с контекстными менеджерами. Если фикстура возвращает путь или дескриптор из блока with, диспетчер контекста может закрыть или удалить ресурс до того, как тест его использует. Сохраняйте диспетчер контекстов открытым в yield, когда тесту нужен управляемый ресурс, или намеренно закройте ресурс перед возвратом, чтобы позже его очистить.
Управление объёмом работ
По умолчанию средства pytest используют function область. Фикстура с областью видимости функции создается, когда её запрашивает тест, и уничтожается в конце этого теста. Если в фикстуре используется код завершения, pytest запускает этот код завершения в конце сферы действия фикстуры.
Pytest также позволяет использовать более широкие области светильников для оптимизации дорогостоящих работ по настройке. Встроенные имена областей светильника:
-
function: область по умолчанию. Светильник создается один раз для каждой тестовой функции. -
class: светильник создается один раз для каждого тестового класса. -
module: исправление создается один раз для модуля. -
package: Фикстура создается один раз для пакета, в котором она определена, и разрушается после последнего теста в этом пакете, включая тесты в подпакетах и подкаталогах. -
session: Фикстура создается один раз для тестовой сессии.
Для расширенных случаев scope может быть вызываемым объектом, который возвращает одно из этих имен областей видимости. Pytest вызывает функцию один раз при определении фикстуры и передает ключевые аргументы fixture_name и config. Для получения дополнительной информации см. документацию pytest о динамической области.
В этом контексте создается один раз означает, что pytest кэширует значение фикстуры для указанной области. Например, фикстура с областью действия на уровне модуля может запрашиваться несколькими тестами в одном модуле, но эти тесты получают одно и то же кэшированное значение фикстуры. Параметризованная фикстура является исключением: pytest может создать новое значение для каждого набора параметров в области действия.
Сохраняйте ограничения функций, если настройка общего доступа не является безопасной и полезной. Изменяемые объекты, открытые подключения, изменения среды и файлы могут приводить к утечке состояния между тестами, когда они кэшируются большим объемом.
Вот как будет выглядеть светильник tmp_file в контексте модуля:
from pathlib import Path
import tempfile
import pytest
@pytest.fixture(scope="module")
def tmp_file():
def create(contents=""):
temp = tempfile.NamedTemporaryFile(delete=False)
try:
path = Path(temp.name)
finally:
temp.close()
path.write_text(contents, encoding="utf-8")
return path
return create
Так как эта фикстура возвращает функцию фабрики, scope="module" кэширует её для модуля. Фабрика по-прежнему создает новый файл при каждом вызове tmp_file()теста. Если средство создало и вернуло путь к файлу напрямую, то каждый тест в модуле получит тот же путь.
Управление очисткой
Предыдущие версии светильника tmp_file создают временные файлы, но они не удаляют эти файлы. Pytest рекомендует yield светильники для простой очистки. Код перед yield выполняет настройку и передает значение для теста. Код после yield выполняется во время разборки, даже если тест завершается сбоем после завершения настройки.
yield Если фиксатор выдает ошибку до его достижения yield, pytest не запускает код завершения после этого yield, но он по-прежнему завершает все предыдущие фиксаторы, которые успешно завершены для того же теста.
Следующая версия отслеживает каждый файл, созданный фабрикой, и пытается удалить эти файлы после завершения теста:
from pathlib import Path
import tempfile
import pytest
@pytest.fixture
def tmp_file():
paths = []
def create(contents=""):
temp = tempfile.NamedTemporaryFile(delete=False)
try:
path = Path(temp.name)
finally:
temp.close()
paths.append(path)
path.write_text(contents, encoding="utf-8")
return path
yield create
for path in paths:
try:
path.unlink()
except FileNotFoundError:
pass
В области по умолчанию function pytest запускает код очистки после каждого теста, который запрашивает tmp_file. Если вы измените фикстуру на scope="module", pytest запускает очистку во время разрушения модуля, после того как pytest завершает выполнение тестов в этом модуле. Если несколько yield функций активны, pytest запускает их код завершения в порядке, обратном порядку установки.
Очистка используется try/except FileNotFoundError , так как тест может удалить сам файл. В Windows удаление также может завершиться ошибкой PermissionError, если тест оставляет файл открытым, поэтому убедитесь, что тесты закрывают все созданные ими дескрипторы. Вы также можете использовать request.addfinalizer() для очистки, если вам нужен дополнительный контроль. Регистрируйте финализатор только после создания ресурса, так как pytest запускает добавленный финализатор во время разборки, даже если фикстура позже вызывает исключение. Для простой очистки yield светильники обычно проще читать.
Использование conftest.py
Вместо определения фикстур в каждом тестовом файле, можно сохранить общие фикстуры в файле с именем conftest.py. Исправления в conftest.py автоматически доступны для тестов в одном каталоге и его подкаталогах без явного импорта.
В наборе тестов можно использовать несколько conftest.py файлов. Pytest определяет доступность фикстур с точки зрения запрашивающего теста: фикстуры в тестовом классе или модуле считаются в этой локальной области, а затем фикстуры из conftest.py файлов в том же каталоге и родительских каталогах. Тесты могут выполнять поиск вверх по фикстурам, но они не ищут вниз в параллельных или дочерних каталогах. Подключаемые светильники ищутся после локальных светильников. Не импортируйте из conftest.py тестовых файлов; пусть pytest обнаруживает его.
Изучение встроенных светильников
Pytest имеет множество встроенных светильников, предназначенных для упрощения тестирования. Эти средства обрабатывают распространенные задачи настройки и очистки, чтобы вы могли сосредоточиться на написании тестовых утверждений. Чтобы просмотреть полный список, ознакомьтесь со ссылкой на pytest для встроенных светильников.
К встроенным светильникам относятся следующие:
-
cache: сохраняет и извлекает значения во время выполнения pytest. -
capsys: записывает текст, записанный вstdoutиstderr. -
tmp_path: предоставляетpathlib.Pathобъект для временного каталога, уникального для каждого вызова тестовой функции, включая каждый параметризованный случай. -
tmpdir: предоставляет временный каталог в качестве устаревшегоpy.path.localобъекта. Для новых тестов pytest рекомендуетtmp_path. -
monkeypatch: временно изменяет классы, функции, словари, переменные среды и другие объекты. -
request: предоставляет сведения о текущем выполнении теста или набора данных и поддерживает финализаторы.
Другие часто используемые встроенные фикстуры включают caplog (перехватывает записи журналов), capfd (перехватывает выходные данные уровня файлового дескриптора), tmp_path_factory (создает временные каталоги на уровне сеанса) и pytestconfig (предоставляет активную конфигурацию pytest). Полный список см. в справочнике по pytest для встроенных светильников.
Следующее упражнение использует tmp_path и pathlib.Path, чтобы создать временные файлы в каталоге, предназначенном для каждого теста. Возможно, вы встретите tmpdir в существующих проектах pytest, но для нового кода предпочтительна tmp_path. По умолчанию pytest сохраняет временные каталоги из последних нескольких запусков тестов, поэтому появление последних временных папок, управляемых pytest, после выполнения тестов ожидаемо. Дополнительные сведения см. в документации pytest для временных каталогов и файлов.
Роль monkeypatching в тестировании
Предыдущие светильники создали и очистили тестовые ресурсы. Другое распространенное использование фикстур — временное изменение состояния в тестах, которые должны изолировать код от внешнего состояния. В таких случаях pytest предоставляет встроенную monkeypatch фикстуру, которая сама по себе является фикстурой pytest и хорошим примером автоматического завершения.
Тестирование кода, зависящее от внешних ресурсов, глобальной конфигурации или состояния операционной системы, может быть сложной задачей. Компонент monkeypatch позволяет временно изменять атрибуты, значения словарей, переменные окружения, текущий рабочий каталог или sys.path для тестирования. Pytest автоматически отменяет эти изменения после завершения запроса тестовой функции или исправления.
Ниже приведен пример замены атрибута функции на небольшой модуль проекта для одного теста. Исправление узко ограниченного кода приложения (вместо функций стандартной библиотеки, используемых самим pytest) позволяет сосредоточиться на тестировании и избежать случайного нарушения собственного поведения pytest:
# project_module.py
def get_user_id():
# Imagine this calls a remote service.
return "real-user"
# test_project.py
import project_module
def test_user_id(monkeypatch):
monkeypatch.setattr(project_module, "get_user_id", lambda: "test-user")
assert project_module.get_user_id() == "test-user"
Можно также указать целевой объект в виде строки импорта с точками. Pytest разрешает и импортирует именованный модуль при применении патча, что удобно, когда тестовому файлу модуль еще не требуется.
import project_module Позже в этом примере тест будет ссылаться на исправленную функцию для подтверждения:
# test_project.py
def test_user_id_by_path(monkeypatch):
monkeypatch.setattr("project_module.get_user_id", lambda: "test-user")
import project_module
assert project_module.get_user_id() == "test-user"
ru-RU: Помимо задания атрибутов и переопределения методов с помощью setattr(), фикстура может удалять атрибуты с помощью delattr(), устанавливать и удалять переменные среды (setenv, delenv), устанавливать и удалять элементы словаря (setitem, delitem), добавлять в начало sys.path (syspath_prepend), изменять текущий рабочий каталог (chdir) и создавать вложенную область исправления с context(). Поведение по умолчанию raising=True для setattr() и delattr() помогает обнаруживать ошибочно написанные цели. Используйте monkeypatch.context(), когда патч должен быть отменен перед началом остальных этапов теста или разборки фикстуры. Дополнительные сведения см. в документации pytest по монкипатчу.
Хотя monkeypatch мощный, используйте его осторожно.
-
Ясность кода и обслуживание: чрезмерное использование
monkeypatchили использование его сложными способами может сделать тесты более сложными для понимания и обслуживания. - Проверка действительности: Monkeypatching может создавать тесты, которые проходят в искусственных условиях, значительно отличающихся от поведения в производственной среде.
- Чрезмерная зависимость от деталей реализации: тесты, которые зависят от внутренних деталей реализации, могут стать хрупкими при изменении основного кода.
-
Сложность отладки. Отладка тестов, которые используются
monkeypatch, может быть сложнее, если исправление изменяет базовое поведение приложения.
Используйте monkeypatch, когда это помогает изолировать целенаправленное поведение, и старайтесь, чтобы каждый патч был как можно более малым и четким.