2015-01-27 6 views
7

Hintergrund

Ich bin ein py.test mit einem fixture in einem conftest file läuft. Sie können den Code unten sehen (dies alles funktioniert gut):Wie überspringe ich einen Pytest mit einer externen Vorrichtung?

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" 

Problem

Jetzt möchte ich in der Lage sein, einige Tests zu überspringen, die nicht für meinen aktuellen Testlauf gelten. In meinem Beispiel führe ich Tests entweder für iOS oder Android (Dies ist nur für Demonstrationszwecke und könnte jeder andere Ausdruck sein).

Leider kann ich nicht bekommen Ahold (mein extern definiert Befestigung) platform_external in der skipif Aussage. Wenn ich den Code unten ausführe, erhalte ich die folgende Ausnahme: NameError: name 'platform_external' is not defined. Ich weiß nicht, ob dies ein py.test Bug als vor Ort ist definierte Fixtures arbeiten.

Add-on für 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') 

Also dachte ich, ich will nur meinen eigenen Dekorateur schaffen, nur um zu sehen, dass es nicht die Geräte als Parameter erhalten:

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') 

Frage

Wie kann ich definieren eine fix in einem conftest Datei und verwenden Sie es (unter Vorbehalt) einen Test überspringen?

Antwort

15

Es scheint, dass py.test die Testadapter bei der Auswertung des Ausdrucks für skipif nicht verwendet. In Ihrem Beispiel ist test_ios tatsächlich erfolgreich, weil es die Funktionplatform im Namensraum des Moduls mit der "ios" Zeichenfolge vergleicht, die False auswertet, daher wird der Test ausgeführt und ist erfolgreich. Wenn pytest das Fixture wie erwartet zur Auswertung einfügt, sollte dieser Test übersprungen worden sein.

Eine Lösung für Ihr Problem (allerdings nicht auf Ihre Frage) wäre eine Befestigung zu implementieren, die Markierungen in den Tests überprüft und überspringt sie entsprechend:

# conftest.py 
import pytest 

@pytest.fixture 
def platform(): 
    return "ios" 

@pytest.fixture(autouse=True) 
def skip_by_platform(request, platform): 
    if request.node.get_marker('skip_platform'): 
     if request.node.get_marker('skip_platform').args[0] == platform: 
      pytest.skip('skipped on this platform: {}'.format(platform)) 

Ein wesentlicher Punkt der autouse Parameter ist, die würde diese Vorrichtung automatisch bei allen Tests einschließen.Dann können Ihre Tests markieren, welche Plattformen wie folgt überspringen:

@pytest.mark.skip_platform('ios') 
def test_ios(platform, request): 
    assert 0, 'should be skipped' 

Hoffe, dass hilft!

+0

Danke - ich wählte auch den Marker gestern als Arbeit um, aber hat es nicht gefallen, da es wie bei Ihnen nicht so elegant war. (Ich habe 'pytest_runtest_setup' für die Markierungsprüfung verwendet). Aber angesichts der py.tests-Einschränkungen scheint dies die nächste Lösung für meine Frage zu sein, und ich werde meine Frage aktualisieren, um sie auszurichten. –

0

Ich hatte ein ähnliches Problem und ich weiß nicht, ob das für Sie noch relevant ist, aber ich habe vielleicht einen Workaround gefunden, der tun würde, was Sie wollen.

Die Idee ist, die MarkEvaluator Klasse und überschreiben die _getglobals Verfahren zu erweitern zu zwingen fixture Werte in den globalen Satz von Auswerter verwendet hinzuzufügen:

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 

fügen einen Haken Um Anrufe zu testen:

def pytest_runtest_call(item): 
    evalskipif = ExtendedMarkEvaluator(item, "skipif_call") 
    if evalskipif.istrue(): 
     pytest.skip('[CANNOT RUN]' + evalskipif.getexplanation()) 

dann können Sie den Markerverwendenin Ihrem Testfall:

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 

Run

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 ====================== 

Problem

Leider ist dies nur einmal für jede Auswertung Ausdruck arbeitet seit MarkEvaluator verwendet ca ched eval basierend auf dem Ausdruck als Schlüssel, damit das nächste Mal, wenn derselbe Ausdruck getestet wird, das Ergebnis der zwischengespeicherte Wert ist.

Lösung

Der Ausdruck wird in den _istrue Verfahren bewertet. Leider gibt es keine Möglichkeit, den Evaluator so zu konfigurieren, dass Zwischenergebnisse vermieden werden. Der einzige Weg, das Caching zu vermeiden, ist die _istrue Methode zu überschreiben nicht die cached_eval Funktion zu verwenden:

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 

Run

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 ===================== 

nun die Tests übersprungen werden, weil ‚myfixture‘ Wert wurde aktualisiert .

Ich hoffe, es hilft.

Prost

Alex

Verwandte Themen