web-dev-qa-db-ja.com

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があります。この属性はテストで反復されるため、timeline.instances内のすべてのinstanceに対してアサーションが保持される場合にのみテストが合格します。

ただし、実際にやりたいのは、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フィクスチャでは、関数はその戻り値に「なります」が、テストがパラメータ化された時点ではまだ起こっていないようです。希望する方法でテストを設定するにはどうすればよいですか?

27
Kurt Peek
4
Kurt Peek

これは、実際には 間接パラメーター化 によって可能です。

この例では、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__])

この同様の質問 も参照してください。

31
imiric

間接的なパラメーター化、または継承を含む以下の私のハックな解決策の代わりに、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

12
hwjp

間接的なパラメーター化の使用は機能しますが、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フラグを渡す必要がないことです。

4
hwjp

人々はフィクスチャとパラメータは同じものだと考える傾向があるため、これは混乱を招くトピックです。これらは、pytest実行の同じフェーズでは収集されず、収集されません。設計上、パラメーターは、テストがcollected(テストノードのリストが作成中)のときに収集されることを意図していますが、フィクスチャは、テストノードはrunです(テストノードのリストは固定されており、変更できません)。そのため、フィクスチャの内容を使用してテストノードの数を変更することはできません。これがパラメーターの役割です。 my 詳細な回答はこちら も参照してください。

これがあなたの質問に「現状のまま」解決策がない理由です: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

Kurt Peek's answer は、私見があなたの質問に直接関連していないので、私見が混乱を追加する別のトピックに言及しています。言及されており、解決策として受け入れられているので、詳しく説明しましょう。実際、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

これは前の例と同じですが、余分な、おそらく役に立たないレイヤーがあります。注: この説明 も参照してください。

0
smarie

Paramsetは関数を参照しています。おそらく、フィクスチャ関数/ timeline /ではなく、Timeline.instancesを参照することを意味します。

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

おそらく理由がありますが、1つのパラメーターセットにマーク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
0
msudder