#python #pytest
Вопрос:
Я пытаюсь повторно использовать некоторые функции модульного тестирования. Единственной переменной частью на самом деле является набор тестовых данных. Поэтому я инкапсулирую логику тестирования в класс и вводлю фактические тестовые данные при создании объекта.
К сожалению, это, похоже, не работает с @pytest.fixture
декоратором.
Какой-то минимальный код для воспроизведения. Это общее определение моего тестового класса в define_test.py
:
#!/usr/bin/env python3
import pytest
class TestDemo:
def __init__(self, _data: list):
self.__testData = _data
@pytest.fixture(scope='module', params=self.__testData)
def fixture(self, request):
return request.param
def test(self, fixture):
foo = fixture
assert foo is not None
Предполагаемое использование было бы чем-то вроде этого в use_test.py
#!/usr/bin/env python3
import define_test
data1 = [1, 2, 3, 4]
test1 = define_test.TestDemo(data1)
data2 = [4, 3, 2, 1]
test2 = define_test.TestDemo(data2)
Попытка запустить этот код приводит к
define_test.py:9: in TestDemo
???
E NameError: name 'self' is not defined
Я почти уверен, что проблема в том, что нельзя просто применить обычные механизмы OO к pytest
декоратору. Похоже, что либо декоратор оценивается еще до того, как существует экземпляр этого класса, либо он вообще не может обрабатывать экземпляры.
Итак, каков был бы правильный способ достижения цели многократного использования тестов с переменными данными?
Ответ №1:
Возможно, вы сможете выразить это с помощью динамической параметризации, доступ к которой вы можете получить с помощью крючка pytest_generate_tests
в документах есть несколько примеров
Таким образом, вы можете перехватывать тесты во время сбора и вводить свои собственные параметры настройки. Крючок вызывается для каждого теста с metafunc
объектом в качестве аргумента, который предоставляет parameterize
метод
Документация по metafunc находится здесь
Мне кажется, что вы не сможете получить доступ к данным экземпляра, потому что этот код выполняется во время сбора, до инициализации ваших объектов. Но вы можете получить доступ к объекту класса через metafunc.cls
, так что, возможно, вы могли бы поместить свои методы в суперкласс, а затем подкласс этого, чтобы добавить конкретные случаи данных в качестве атрибутов класса
вот несколько примеров кода того, о чем я думаю
а) pytestfoo.py
#!/usr/bin/env python3
import pytest
class BaseTest:
def test(self, testparams):
print("n", testparams, "n")
assert testparams is not None
class TestCaseA(BaseTest):
testparams = [[1,2,3,4]]
class TestCaseB(BaseTest):
testparams = [[4,3,2,1]]
б) соответствующее conftest.py
def pytest_generate_tests(metafunc):
metafunc.parametrize("testparams", metafunc.cls.testparams)
и прогон этих тестов с использованием pytest
pytest -sv pytestfoo.py
========================================================== test session starts ==========================================================
platform linux -- Python 3.7.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- /home/cms/.pyenv/versions/3.7.6/envs/tmp/bin/python3.7
cachedir: .pytest_cache
rootdir: /home/cms/tmp
collected 2 items
pytestfoo.py::TestCaseA::test[testparams0]
[1, 2, 3, 4]
PASSED
pytestfoo.py::TestCaseB::test[testparams0]
[4, 3, 2, 1]
PASSED
=========================================================== 2 passed in 0.01s ===========================================================
надеюсь, здесь найдется немного вдохновения для ваших собственных экспериментов. Документы pytest показывают, как параматериализовать данные из командной строки, что может быть более практичным, чем описанный выше подход, но я пытался следовать вашему примеру в определении данных с помощью кода.
Комментарии:
1. Спасибо за указания, @cms. Я буду изучать эти направления.
2. @twil всегда пожалуйста! Я обновил, чтобы включить некоторые примеры кода игрушек подхода, который я предлагал
3. Спасибо за дополнительный пример кода и за оспаривание первоначального запроса на решение этой проблемы с помощью атрибутов экземпляра. Это была только моя первая, наивная попытка. Если есть другой/лучший подход для отсрочки ввода данных и расширения решения, это так же хорошо. Хороший пример для «давайте людям то, что им нужно, а не то, что они хотят». Теперь позвольте мне попробовать варианты….
4. Хорошо, опробовав решение, предложенное @cms, я могу подтвердить, что оно технически соответствует требованию о перехвате параметризации теста во время сбора. Вот почему я отмечаю это как принятый ответ. Копаясь глубже в теме, которую я нашел, это
pytest_generate_tests
будет выполняться для каждого теста в модуле. Это приведет к некоторому расширению условного кода в этой функции или необходимости организации тестов в нескольких модулях. Мне нужно будет посмотреть, практично ли это для больших наборов тестов.