py.test を conftest file で fixture と一緒に実行しています。以下のコードを見ることができます(これはすべて正常に機能します):
example_test.py
import pytest
@pytest.fixture
def platform():
return "ios"
@pytest.mark.skipif("platform == 'ios'")
def test_ios(platform):
if platform != 'ios':
raise Exception('not ios')
def test_Android_external(platform_external):
if platform_external != 'Android':
raise Exception('not Android')
conftest.py
import pytest
@pytest.fixture
def platform_external():
return "Android"
現在のテスト実行に適用されないいくつかのテストをスキップできるようにしたいと思います。私の例では、iOSまたはAndroidのいずれかのテストを実行しています(これデモンストレーションのみを目的としており、他の表現でもかまいません)。
残念ながら、(私の外部定義フィクスチャ)platform_external
をskipif
で取得できません。ステートメント。以下のコードを実行すると、次の例外が発生します:NameError: name 'platform_external' is not defined
。 ローカルで定義されたフィクスチャが機能しているため、これがpy.testバグであるかどうかはわかりません。
example_test.pyのアドオン
@pytest.mark.skipif("platform_external == 'Android'")
def test_Android(platform_external):
"""This test will fail as 'platform_external' is not available in the decorator.
It is only available for the function parameter."""
if platform_external != 'Android':
raise Exception('not Android')
だから私は自分のデコレータを作成して、パラメータとしてフィクスチャを受け取らないことを確認しようと思いました:
from functools import wraps
def platform_custom_decorator(func):
@wraps(func)
def func_wrapper(*args, **kwargs):
return func(*args, **kwargs)
return func_wrapper
@platform_custom_decorator
def test_Android_2(platform_external):
"""This test will also fail as 'platform_external' will not be given to the
decorator."""
if platform_external != 'Android':
raise Exception('not Android')
conftestファイルでfixtureを定義し、それを使用して(条件付き)テストをスキップ?
Py.testは、skipif
の式を評価するときにテストフィクスチャを使用しないようです。あなたの例では、test_ios
は、モジュールの名前空間にあるfunctionplatform
を"ios"
文字列と比較しているため、実際に成功しています。この文字列はFalse
と評価されるため、テストが実行され、成功します。 pytestが期待どおりに評価のためにフィクスチャを挿入していた場合、そのテストはスキップされているはずです。
あなたの問題の解決策(あなたの質問ではありませんが)は、テストのマークを検査し、それに応じてそれらをスキップするフィクスチャを実装することです:
# conftest.py
import pytest
@pytest.fixture
def platform():
return "ios"
@pytest.fixture(autouse=True)
def skip_by_platform(request, platform):
if request.node.get_closest_marker('skip_platform'):
if request.node.get_closest_marker('skip_platform').args[0] == platform:
pytest.skip('skipped on this platform: {}'.format(platform))
重要な点はautouse
パラメーターです。これにより、そのフィクスチャがすべてのテストに自動的に含まれるようになります。次に、テストでスキップするプラットフォームを次のようにマークできます。
@pytest.mark.skip_platform('ios')
def test_ios(platform, request):
assert 0, 'should be skipped'
お役に立てば幸いです。
この answer から別のSO質問へのインスピレーションを使用して、私はこの問題に対してこのアプローチを使用しています。これはうまく機能します。
import pytest
@pytest.fixture(scope='session')
def requires_something(request):
something = 'a_thing'
if request.param != something:
pytest.skip(f"Test requires {request.param} but environment has {something}")
@pytest.mark.parametrize('requires_something',('something_else',), indirect=True)
def test_indirect(requires_something):
print("Executing test: test_indirect")
Bruno Oliveiraのソリューションは機能していますが、新しいpytest(> = 3.5.0)の場合は、pytest_configureを追加する必要があります。
# conftest.py
import pytest
@pytest.fixture
def platform():
return "ios"
@pytest.fixture(autouse=True)
def skip_by_platform(request, platform):
if request.node.get_closest_marker('skip_platform'):
if request.node.get_closest_marker('skip_platform').args[0] == platform:
pytest.skip('skipped on this platform: {}'.format(platform))
def pytest_configure(config):
config.addinivalue_line(
"markers", "skip_by_platform(platform): skip test for the given search engine",
)
使用する:
@pytest.mark.skip_platform('ios')
def test_ios(platform, request):
assert 0, 'should be skipped'
私も同様の問題を抱えていて、これがまだあなたに関係があるかどうかはわかりませんが、あなたが望むことをする回避策を見つけたかもしれません。
アイデアは、MarkEvaluator
クラスを拡張し、_getglobals
メソッドをオーバーライドして、エバリュエーターが使用するグローバルセットにフィクスチャ値を強制的に追加することです。
conftest.py
from _pytest.skipping import MarkEvaluator
class ExtendedMarkEvaluator(MarkEvaluator):
def _getglobals(self):
d = super()._getglobals()
d.update(self.item._request._fixture_values)
return d
呼び出しをテストするためのフックを追加します。
def pytest_runtest_call(item):
evalskipif = ExtendedMarkEvaluator(item, "skipif_call")
if evalskipif.istrue():
pytest.skip('[CANNOT RUN]' + evalskipif.getexplanation())
次に、テストケースでマーカーskipif_call
を使用できます。
test_example.py
class Machine():
def __init__(self, state):
self.state = state
@pytest.fixture
def myfixture(request):
return Machine("running")
@pytest.mark.skipif_call('myfixture.state != "running"')
def test_my_fixture_running_success(myfixture):
print(myfixture.state)
myfixture.state = "stopped"
assert True
@pytest.mark.skipif_call('myfixture.state != "running"')
def test_my_fixture_running_fail(myfixture):
print(myfixture.state)
assert False
@pytest.mark.skipif_call('myfixture.state != "stopped"')
def test_my_fixture_stopped_success(myfixture):
print(myfixture.state)
myfixture.state = "running"
@pytest.mark.skipif_call('myfixture.state != "stopped"')
def test_my_fixture_stopped_fail(myfixture):
print(myfixture.state)
assert False
実行
pytest -v --tb=line
============================= test session starts =============================
[...]
collected 4 items
test_example.py::test_my_fixture_running_success PASSED
test_example.py::test_my_fixture_running_fail FAILED
test_example.py::test_my_fixture_stopped_success PASSED
test_example.py::test_my_fixture_stopped_fail FAILED
================================== FAILURES ===================================
C:\test_example.py:21: assert False
C:\test_example.py:31: assert False
===================== 2 failed, 2 passed in 0.16 seconds ======================
問題
残念ながら、MarkEvaluatorは式に基づくキャッシュされたevalをキーとして使用するため、これは各評価式に対して1回だけ機能します。したがって、次に同じ式がテストされるときに、結果はキャッシュされた値になります。
解決策
式は_istrue
メソッドで評価されます。残念ながら、結果のキャッシュを回避するようにエバリュエーターを構成する方法はありません。キャッシュを回避する唯一の方法は、_istrue
メソッドをオーバーライドしてcached_eval関数を使用しないことです。
class ExtendedMarkEvaluator(MarkEvaluator):
def _getglobals(self):
d = super()._getglobals()
d.update(self.item._request._fixture_values)
return d
def _istrue(self):
if self.holder:
self.result = False
args = self.holder.args
kwargs = self.holder.kwargs
for expr in args:
import _pytest._code
self.expr = expr
d = self._getglobals()
# Non cached eval to reload fixture values
exprcode = _pytest._code.compile(expr, mode="eval")
result = eval(exprcode, d)
if result:
self.result = True
self.reason = expr
self.expr = expr
break
return self.result
return False
実行
pytest -v --tb=line
============================= test session starts =============================
[...]
collected 4 items
test_example.py::test_my_fixture_running_success PASSED
test_example.py::test_my_fixture_running_fail SKIPPED
test_example.py::test_my_fixture_stopped_success PASSED
test_example.py::test_my_fixture_stopped_fail SKIPPED
===================== 2 passed, 2 skipped in 0.10 seconds =====================
'myfixture'値が更新されたため、テストはスキップされます。
それが役に立てば幸い。
乾杯
アレックス