2015-01-27 7 views
7

Sfondo

Sono in esecuzione di un py.test con un fixture in una conftest file. Si può vedere il codice qui sotto (questo tutto funziona bene):Come saltare un pytest usando un dispositivo esterno?

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" 

Problema

Ora voglio essere in grado di saltare alcuni test che non si applicano alla mia prova corrente. Nel mio esempio eseguo test sia per iOS o Android (Questo è solo a scopo dimostrativo e potrebbe essere qualsiasi altra espressione).

Purtroppo non riesco a mettermi in contatto con (il mio esternamente definito apparecchio) platform_external nella dichiarazione skipif. Quando eseguo il codice seguente, ricevo la seguente eccezione: NameError: name 'platform_external' is not defined. Non so se si tratta di un bug py.test come localmente funzionante.

add-on per 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') 

Così ho pensato che mi limiterò a creare il mio decoratrice, solo per vedere che non riceverà gli infissi come parametri:

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

domanda

Come posso definire una correzione tura in un file conftest e utilizzarlo per (condizionatamente) saltare un test?

risposta

15

Sembra che py.test non utilizzi i dispositivi di prova durante la valutazione dell'espressione per skipif. Con il tuo esempio, test_ios ha effettivamente successo perché sta confrontando la funzione platform trovata nello spazio dei nomi del modulo alla stringa "ios", che valuta False quindi il test viene eseguito e ha esito positivo. Se pytest stava inserendo la fixture per la valutazione come previsto, quel test avrebbe dovuto essere saltato.

Una soluzione al vostro problema (non alla tua domanda però) sarebbe quello di realizzare un apparecchio che ispeziona i marchi nelle prove, e li salta di conseguenza:

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

Un punto chiave è il parametro autouse, che renderebbe questo dispositivo automaticamente incluso in tutti i test.Quindi i tuoi test possono contrassegnare quali piattaforme saltare in questo modo:

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

Sperare che aiuti!

+0

Grazie - Ho anche scelto il marcatore ieri come un lavoro in giro, ma non mi è piaciuto come non era elegante come la tua. (Ho usato 'pytest_runtest_setup' per il controllo dei marker). Ma dati i vincoli di py.tests questa sembra la soluzione più vicina alla mia domanda e aggiornerò la mia domanda per allinearla. –

0

Ho avuto un problema simile e non so se questo è ancora rilevante per voi, ma potrei aver trovato una soluzione che avrebbe fatto ciò che volete.

L'idea è di estendere la classe MarkEvaluator e sovrascrivere il metodo _getglobals alla forza per aggiungere valori fissaggio nel set globale utilizzato dal valutatore:

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 

aggiungere un gancio per verificare le chiamate:

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

quindi è possibile utilizzare il marker skipif_call in caso di test:

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

Problema

Purtroppo, questo funziona solo una volta per ogni espressione di valutazione in quanto MarkEvaluator utilizza ca ched eval in base all'espressione come chiave, quindi la prossima volta che verrà testata la stessa espressione, il risultato sarà il valore memorizzato nella cache.

Soluzione

l'espressione viene valutata nel metodo _istrue. Sfortunatamente non c'è modo di configurare il programma di valutazione per evitare i risultati di memorizzazione nella cache. L'unico modo per evitare la memorizzazione nella cache è quello di sovrascrivere il metodo _istrue non utilizzare la funzione 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 

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

Ora i test vengono saltati a causa valore 'myfixture' è stato aggiornato .

Spero che aiuti.

Acclamazioni

Alex