Как параметризовать прибор Pytest

Рассмотрим следующий Pytest:

import pytest

class TimeLine(object):
    instances = [0, 1, 2]

@pytest.fixture
def timeline():
    return TimeLine()

def test_timeline(timeline):
    for instance in timeline.instances:
        assert instance % 2 == 0

if __name__ == "__main__":
    pytest.main([__file__])

В тесте test_timeline используется прибор Pytest, timeline, который сам имеет атрибут instances. Этот атрибут повторяется в тесте, так что тест проходит, только если утверждение выполняется для каждого instance в timeline.instances.

Тем не менее, я действительно хотел бы создать 3 теста, 2 из которых должны пройти, и 1 из них потерпит неудачу. Я пробовал
@pytest.mark.parametrize("instance", timeline.instances)
def test_timeline(timeline):
    assert instance % 2 == 0

но это приводит к

AttributeError: 'function' object has no attribute 'instances'

Как я понимаю, в приборах Pytest функция "становится" ее возвращаемым значением, но это, похоже, еще не произошло в то время, когда тест параметризуется. Как настроить тест по желанию?

Ответ 2

Это возможно с помощью косвенной параметризации.

В этом примере выполняется то, что вы хотите с помощью pytest 3.1.2:

import pytest

class TimeLine:
    def __init__(self, instances):
        self.instances = instances

@pytest.fixture
def timeline(request):
    return TimeLine(request.param)

@pytest.mark.parametrize(
    'timeline',
    ([1, 2, 3], [2, 4, 6], [6, 8, 10]),
    indirect=True
)
def test_timeline(timeline):
    for instance in timeline.instances:
        assert instance % 2 == 0

if __name__ == "__main__":
    pytest.main([__file__])

Также см. этот похожий вопрос.

Ответ 3

вместо косвенной параметризации или моего хакерского решения, приведенного ниже, включающего наследование, вы также можете просто использовать аргумент params для @pytest.fixture() - я думаю, что это, возможно, самое простое решение?

import pytest

class TimeLine:
    def __init__(self, instances=[0, 0, 0]):
        self.instances = instances


@pytest.fixture(params=[
    [1, 2, 3], [2, 4, 5], [6, 8, 10]
])
def timeline(request):
    return TimeLine(request.param)


def test_timeline(timeline):
    for instance in timeline.instances:
        assert instance % 2 == 0

https://docs.pytest.org/en/latest/fixture.html#parametrizing-fixtures

Ответ 4

Использование косвенной параметризации работает, но я нахожу необходимость использовать request.param как магическую неназванную переменную, немного странную.

Вот образец, который я использовал. Это неловко по-другому, возможно, но, возможно, вы тоже предпочтете это!

import pytest

class TimeLine:
    def __init__(self, instances):
        self.instances = instances


@pytest.fixture
def instances():
    return [0, 0, 0]


@pytest.fixture
def timeline(instances):
    return TimeLine(instances)


@pytest.mark.parametrize('instances', [
    [1, 2, 3], [2, 4, 5], [6, 8, 10]
])
def test_timeline(timeline):
    for instance in timeline.instances:
        assert instance % 2 == 0

фиксатор timeline зависит от другого фиксатора, называемого instances, который имеет значение по умолчанию [0,0,0], но в реальном тесте мы используем parametrize чтобы ввести ряд различных значений для instances.

На мой взгляд, преимущество в том, что у всего есть хорошее имя, плюс вам не нужно передавать этот indirect=True флаг indirect=True.

Ответ 5

Это запутанная тема, потому что люди склонны думать, что приборы и параметры - это одно и то же. Они не собраны и даже не собраны на одной и той же стадии пробега. Конструктивны параметры предназначены быть собраны, когда тесты собираются (список тестовых узлов находится в стадии строительства), в то время как светильники предназначены для исполнения, когда тестовые узлы запуска (список тестовых узлов фиксирован и не может быть изменен). Таким образом, содержимое прибора не может использоваться для изменения количества тестовых узлов - это роль параметров. Смотрите также мой подробный ответ здесь.

Вот почему ваш вопрос не имеет решения "как есть": вы должны помещать экземпляры Timeline шкалы в параметр, а не в фиксатор. Как это:

import pytest

class TimeLine(object):
    instances = [0, 1, 2]

@pytest.fixture(params=TimeLine().instances)
def timeline(request):
    return request.param

def test_timeline(timeline):
    assert timeline % 2 == 0

В ответе Курта Пика упоминается еще одна тема, которая, по-моему, добавляет здесь путаницу, поскольку она не имеет прямого отношения к вашему вопросу. Поскольку он упоминается и даже принимается в качестве решения, позвольте мне уточнить: действительно, в pytest вы не можете использовать осветитель в @pytest.mark.parametrize. Но даже если бы ты мог, это ничего не изменит.

Поскольку эта функция теперь доступна в pytest-cases (я автор), вы можете попробовать сами. Единственный способ заставить ваш пример работать даже с этой функцией - все тот же: удалить список из прибора. Итак, вы в конечном итоге:

import pytest
from pytest_cases import pytest_parametrize_plus, fixture_ref

class TimeLine(object):
    instances = [0, 1, 2]

@pytest.fixture(params=TimeLine().instances)
def timeline(request):
    return request.param

@pytest_parametrize_plus("t", [fixture_ref(timeline)])
def test_timeline(t):
    assert t % 2 == 0

То же самое, что и в предыдущем примере, с дополнительным, возможно, бесполезным слоем. Примечание: см. Также это обсуждение.

Ответ 6

Вы параметризуете paramset, ссылающийся на функцию. Возможно, вы имеете в виду ссылку на Timeline.instances, а не на функцию фиксации /timeline/:

@pytest.mark.parametrize("instance", Timeline.instances)
def test_func(instance):
    pass

Я думаю, что вы хотите добавить метку xfail к одному набору параметров, возможно, по какой-то причине?

def make_my_tests():
    return [0, 2, mark.xfail(1, reason='not even')]

@mark.parametrize('timeline', paramset=make_my_tests(), indirect=True)
def test_func(timeline):
    assert timeline % 2 == 0

Ответ 7

Я решил создать функцию без декоратора fixture и функции, которая является предыдущей функцией с примененным декоратором:

def one_raw():
    return 1

one = pytest.fixture(one_raw)

def test_add(one):
    assert one + 2 == 3

@pytest.mark.parametrize("subtrahend", (one_raw(), int(True)))
def test_sub(subtrahend):
    assert 4 - subtrahend == 3