pytest: Как использовать переменные экземпляра тестового класса в качестве параметров приспособления

#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 будет выполняться для каждого теста в модуле. Это приведет к некоторому расширению условного кода в этой функции или необходимости организации тестов в нескольких модулях. Мне нужно будет посмотреть, практично ли это для больших наборов тестов.