2012-09-13 11 views
24

Sto creando i casi di test per i web-test usando i framework Jenkins, Python, Selenium2 (webdriver) e Py.test.Pytest: come saltare il resto dei test nella classe se uno ha fallito?

Finora sto organizzando il mio test nella seguente struttura:

ogni Classe è il caso di test e ogni test_ metodo è un passo test.

Questa configurazione funziona ECCELLENTE quando tutto funziona correttamente, tuttavia quando un passaggio si interrompe il resto dei "passaggi di prova" impazzisce. Sono in grado di contenere l'errore all'interno della classe (Test Case) con l'aiuto di teardown_class(), tuttavia sto cercando in che modo migliorarlo.

Ciò di cui ho bisogno è in qualche modo saltare (o xfail) il resto dei metodi test_ all'interno di una classe se uno di essi non è riuscito, in modo che il resto dei casi di test non vengano eseguiti e contrassegnati come FAILED (poiché ciò sarebbe falso positivo)

Grazie!

UPDATE: Non sto guardando o la risposta "è una cattiva pratica", dal momento che chiamarla in quel modo è molto discutibile. (ogni classe di test è indipendente e dovrebbe essere sufficiente).

UPDATE 2: L'inserimento della condizione "se" in ogni metodo di test non è un'opzione - È MOLTO lavoro ripetuto. Quello che sto cercando è (forse) qualcuno sa come usare il hooks per i metodi di classe.

+0

Non puoi semplicemente impostare il flag --maxfail su 1? Ciò renderebbe py.test fine se un test fallisce. – Nacht

+0

@Nacht l'idea è di continuare a testare altri casi di test nonostante uno di essi abbia fallito, ma interrompere qualsiasi test-step nel caso di test fallito –

risposta

22

Mi piace l'idea generale del "test-step". Lo definirei come test "incrementale" e ha più senso negli scenari di test funzionali IMHO.

Ecco un un'implementazione che non dipende dettagli interni di pytest (tranne che per le estensioni ufficiali gancio):

import pytest 

def pytest_runtest_makereport(item, call): 
    if "incremental" in item.keywords: 
     if call.excinfo is not None: 
      parent = item.parent 
      parent._previousfailed = item 

def pytest_runtest_setup(item): 
    previousfailed = getattr(item.parent, "_previousfailed", None) 
    if previousfailed is not None: 
     pytest.xfail("previous test failed (%s)" %previousfailed.name) 

Se ora avete un "test_step.py" come questo:

import pytest 

@pytest.mark.incremental 
class TestUserHandling: 
    def test_login(self): 
     pass 
    def test_modification(self): 
     assert 0 
    def test_deletion(self): 
     pass 

poi eseguire sembra che questo (usando -rx a riferire sui motivi XFAIL):

(1)[email protected]:~/p/pytest/doc/en/example/teststep$ py.test -rx 
============================= test session starts ============================== 
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev17 
plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov, timeout 
collected 3 items 

test_step.py .Fx 

=================================== FAILURES =================================== 
______________________ TestUserHandling.test_modification ______________________ 

self = <test_step.TestUserHandling instance at 0x1e0d9e0> 

    def test_modification(self): 
>  assert 0 
E  assert 0 

test_step.py:8: AssertionError 
=========================== short test summary info ============================ 
XFAIL test_step.py::TestUserHandling::()::test_deletion 
    reason: previous test failed (test_modification) 
================ 1 failed, 1 passed, 1 xfailed in 0.02 seconds ================= 

Sto usando "xfail" qui perché i salti sono piuttosto per ambienti sbagliati o dipendenze mancanti, versioni errate dell'interprete.

Modifica: nota che né il tuo esempio né il mio esempio funzionerebbero direttamente con test distribuiti. Per questo, il plugin pytest-xdist ha bisogno di sviluppare un modo per definire gruppi/classi da inviare a vendita intera a uno slave di prova invece della modalità corrente che di solito invia le funzioni di test di una classe a diversi slave.

+0

Grazie Holger. Sapevo che probabilmente stavo andando troppo in profondità nel codice py.test con la mia soluzione. :) Proverò sicuramente il tuo suggerimento. Inoltre, sto creando i casi di test funzionali per l'automazione dell'interfaccia utente web (ne ho parlato molto ultimamente, perché mi sento come se stessi "facendo girare il nido del calabrone" con la mia implementazione "test-step") :) As per il plugin "pytest-xdist" - Non l'ho ancora provato, tuttavia, sebbene distribuisca i moduli. In ogni caso, posso usare ** Jenkins ** per distribuire i test tra gli schiavi. –

+0

Inoltre, poiché ogni * passaggio * modifica ulteriormente lo stato dell'applicazione, una teardown di classe generale non è realmente un'opzione. Ecco perché sto cercando di incrementare progressivamente il "teardown" della classe. (come qui http://stackoverflow.com/questions/12538808/pytest-2-3-adding-teardowns-within-the-class/12573949#12573949) So che hai già postato la risposta lì, tuttavia io non Penso che risponda alla mia domanda. :) Ho postato la mia risposta, puoi commentare? grazie –

+0

Holger Ho appena scoperto questo post perché mi sono trovato nella posizione di averne bisogno! Sarebbe fantastico se questo fosse integrato in un plugin o parte di pytest stesso :) –

4

È generalmente una cattiva pratica fare ciò che stai facendo. Ogni test dovrebbe essere il più indipendente possibile dagli altri, mentre si dipende completamente dai risultati degli altri test.

In ogni caso, leggendo the docs sembra che una funzione come quella che si desidera non sia implementata (probabilmente perché non è stata considerata utile).

Un work-around potrebbe essere quella di "fallire" i test di chiamata di un metodo personalizzato che definisce una condizione sulla classe, e segnare ogni test con il decoratore "skipIf":

class MyTestCase(unittest.TestCase): 
    skip_all = False 

    @pytest.mark.skipIf("MyTestCase.skip_all") 
    def test_A(self): 
     ... 
     if failed: 
      MyTestCase.skip_all = True 
    @pytest.mark.skipIf("MyTestCase.skip_all") 
    def test_B(self): 
     ... 
     if failed: 
      MyTestCase.skip_all = True 

Oppure si può fare questo controllo prima di eseguire ogni test e alla fine chiamare pytest.skip().

modifica: La marcatura come xfail può essere eseguita allo stesso modo, ma utilizzando le corrispondenti chiamate di funzione.

Probabilmente, invece di riscrivere il codice piastra della caldaia per ogni test, è possibile scrivere un decoratore (questo probabilmente richiederebbe che i metodi restituiscano un "flag" che indica se hanno fallito o meno).

In ogni caso, vorrei precisare che, come lei afferma, se uno di questi test fallisce, allora altri test falliti nello stesso caso di test dovrebbero essere considerati falsi positivi ... ma si può fare questo " mano". Basta controllare l'output e individuare i falsi positivi. Anche se questo potrebbe essere noioso/a rischio.

+0

Grazie per la risposta. Forse lo vedo in modo diverso, tuttavia ho riscontrato situazioni di coppia in cui rendere il test completamente indipendente a) non ha senso: ad esempio, collaudo "utente". I casi di test sono: 1) crea utente 2) modifica utente 3) cancella utente. Nei casi di test "create user" dovrò fare il "teardown" - delete user - che è un altro test case per cominciare. Creare un caso di test "Elimina utente" ora non ha senso. Tuttavia, unire questi due casi di test in uno non è giusto dal punto di vista del "test case": forse la creazione ha esito positivo, ma la cancellazione non riesce. Cosa fare? –

+0

b) non potrebbe essere fatto a causa della "precondizione" che richiede tempo: dire che alcuni test richiedono che il PC si trovi in ​​un certo "stato". Questo "stato" è controllato attraverso il sito web e per raggiungere questo stato ho bisogno di 2-3 ore. Dopo di che faccio il resto dei test in questo stato del pc. In questo caso non posso mettere il pc dentro e fuori da questo stato per ogni caso di test - ci vorranno settimane per completare la suite. –

+2

La maggior parte di queste situazioni può essere evitata usando i mock. Non si vuole dipendere da una "condizione del computer". Dovresti semplicemente creare una simulazione e testare come reagisci il codice in quella situazione. Ci sono alcuni casi in cui questo non è possibile ... per esempio se vuoi testare come funziona il programma quando la RAM è quasi piena ecc. Ma penso che questo non dovrebbe essere considerato un test unitario. Questi test controllano alcune interazioni tra uno stato globale e il programma. O si può controllare completamente lo stato globale, o semplicemente non si possono fare test _unit_. – Bakuriu

0

AGGIORNAMENTO: Si prega di dare un'occhiata a @ hpk42 risposta.La sua risposta è meno invadente.

Questo è quello che mi è stato effettivamente cercando:

from _pytest.runner import runtestprotocol 
import pytest 
from _pytest.mark import MarkInfo 

def check_call_report(item, nextitem): 
    """ 
    if test method fails then mark the rest of the test methods as 'skip' 
    also if any of the methods is marked as 'pytest.mark.blocker' then 
    interrupt further testing 
    """ 
    reports = runtestprotocol(item, nextitem=nextitem) 
    for report in reports: 
     if report.when == "call": 
      if report.outcome == "failed": 
       for test_method in item.parent._collected[item.parent._collected.index(item):]: 
        test_method._request.applymarker(pytest.mark.skipif("True")) 
        if test_method.keywords.has_key('blocker') and isinstance(test_method.keywords.get('blocker'), MarkInfo): 
         item.session.shouldstop = "blocker issue has failed or was marked for skipping" 
      break 

def pytest_runtest_protocol(item, nextitem): 
# add to the hook 
    item.ihook.pytest_runtest_logstart(
     nodeid=item.nodeid, location=item.location, 
    ) 
    check_call_report(item, nextitem) 
    return True 

Ora l'aggiunta di questo conftest.py o come plug-in risolve il mio problema.
Inoltre, è stato migliorato per arrestare i test se il test blocker non è riuscito. (Significa che tutti gli altri test sono inutili)

+0

Riesco a vedere l'utilità di un'organizzazione "test-step" come la proponi e la usi. L'implementazione è leggermente hacky, però :) Vedi il mio esempio per un miglioramento. – hpk42

3
2

Si potrebbe desiderare di dare un'occhiata a pytest-dependency. È un plug-in che consente di saltare alcuni test se alcuni altri test non sono riusciti. Nel tuo caso, sembra che i test incrementali discussi da gbonetti siano più rilevanti.

Problemi correlati