2011-10-17 2 views
24

Sto testando le eccezioni con il naso. Ecco un esempio:Come dovremmo testare le eccezioni con il naso?

def testDeleteUserUserNotFound(self): 
    "Test exception is raised when trying to delete non-existent users" 
    try: 
     self.client.deleteUser('10000001-0000-0000-1000-100000000000') 
     # make nose fail here 
    except UserNotFoundException: 
     assert True 

L'asserzione viene eseguito se l'eccezione è sollevata, ma se non fa eccezione viene sollevata, non verrà eseguito.

C'è qualcosa che posso mettere sulla riga commentata sopra in modo che se non viene sollevata alcuna eccezione il naso segnalerà un errore?

+0

quasi un doppio di http://stackoverflow.com/questions/11767938/how-to-use-noses-assert-raises – Stefano

risposta

39

naso fornisce gli strumenti per testare le eccezioni (come unittest fa). Prova questo esempio (e letto su altri strumenti a Nose Testing Tools

from nose.tools import * 

l = [] 
d = dict() 

@raises(Exception) 
def test_Exception1(): 
    '''this test should pass''' 
    l.pop() 

@raises(KeyError) 
def test_Exception2(): 
    '''this test should pass''' 
    d[1] 

@raises(KeyError) 
def test_Exception3(): 
    '''this test should fail (IndexError raised but KeyError was expected)''' 
    l.pop() 

def test_Exception4(): 
    '''this test should fail with KeyError''' 
    d[1] 

penserei che questo è il modo corretto che stavate cercando, perché permette di essere specifico circa le eccezioni che ci si aspetta o desidera Quindi in realtà provochi l'errore di vedere che solleva l'eccezione giusta e poi lascia che il risultato sia (Metti il ​​più possibile la logica nei test di unità!)

+4

Grazie. C'è anche "assert_raises" dall'aspetto delle cose. – BartD

+2

nose espone anche gli assert_raises che sono solo self.assertRaises da unittest. Questo è conveniente per quando è necessario fare di più con l'eccezione nel modulo. con assertRaises (ValueError) come e:

1

Non so cosa sia il naso, ma hai provato a usare 'else' dopo la clausola except. Cioè

else: 
    assert False 
8
def testDeleteUserUserNotFound(self): 
    "Test exception is raised when trying to delete non-existent users" 
    try: 
     self.client.deleteUser('10000001-0000-0000-1000-100000000000') 
     assert False # <--- 
    except UserNotFoundException: 
     assert True 

La semantica try/except implicano che il flusso di esecuzione lascia il blocco try su un'eccezione, quindi assert False non funziona se viene generata un'eccezione. Inoltre, l'esecuzione non reinserirà il blocco try di nuovo dopo che il blocco except è stato eseguito in esecuzione, quindi non si dovrebbe incorrere in problemi.

 ↓ 
(statements) 
    ↓ exception 
    (try) ↚──────────→ (except) 
    ↓     │ 
(statements) ←───────────┘ 
    ↓ 
6

Non so perché non è ancora qui ma esiste un altro modo:

import unittest 

class TestCase(unittest.TestCase): 

    def testKeyError(self): 
     d = dict() 
     with self.assertRaises(KeyError): 
      d[1] 
+1

Questo meccanismo non è menzionato poiché il post riguarda specificamente test con nosetests, che non richiede l'utente di classi o ereditarietà da 'unittests.TestCase' (la classe che definisce il' assertRaises' metodo che hai citato). – PeterJCLaw

2

Uso assert_raises:

from nose.tools import assert_raises 

our_method   = self.client.deleteUser 
arg1    = '10000001-0000-0000-1000-100000000000' 
expected_exception = UserNotFoundException 

assert_raises(expected_exception, our_method, arg1) 

Utilizzando try e catch nei test sembra come una cattiva pratica (la maggior parte dei casi).

Non c'è alcuna documentazione specifica nel naso perché è fondamentalmente solo un wrapper unittest.TestCase.assertRaises (rif. How to use nose's assert_raises?)

4

vi consiglio vivamente di utilizzare assert_raises e assert_raises_regexp da nose.tools, che riprodurre il comportamento di assertRaises e assertRaisesRegexp da unittest.TestCase. Questi consentono di utilizzare la stessa funzionalità fornita da unittest.TestCase in suite di test che non utilizzano effettivamente la classe unittest.TestCase.

Trovo che lo @raises sia uno strumento troppo smussato. Qui è il codice che illustra il problema:

from nose.tools import * 

something = ["aaa", "bbb"] 

def foo(x, source=None): 
    if source is None: 
     source = something 
    return source[x] 

# This is fine 
@raises(IndexError) 
def test1(): 
    foo(3) 

# This is fine. The expected error does not happen because we made 
# a mistake in the test or in the code. The failure indicates we made 
# a mistake. 
@raises(IndexError) 
def test2(): 
    foo(1) 

# This passes for the wrong reasons. 
@raises(IndexError) 
def test3(): 
    source = something[2] # This is the line that raises the exception. 
    foo(10, source) # This is not tested. 

# When we use assert_raises, we can isolate the line where we expect 
# the failure. This causes an error due to the exception raised in 
# the first line of the function. 
def test4(): 
    source = something[2] 
    with assert_raises(IndexError): 
     foo(10, source) 

test3 passaggi, ma non perché foo ha sollevato l'eccezione che ci aspettavamo, ma perché il codice che imposta i dati per essere utilizzati da foo non riesce con la stessa eccezione.test4 mostra come il test può essere scritto usando assert_raises per testare realmente ciò che intendiamo testare. Il problema sulla prima riga causerà a Nose di segnalare un errore e quindi potremo riscrivere il test in modo tale da poter finalmente testare ciò che intendevamo testare.

@raises non consente di testare il messaggio associato all'eccezione. Quando sollevo ValueError, solo per fare un esempio, di solito voglio sollevarlo con un messaggio informativo. Ecco un esempio:

def bar(arg): 
    if arg: # This is incorrect code. 
     raise ValueError("arg should be higher than 3") 

    if arg >= 10: 
     raise ValueError("arg should be less than 10") 

# We don't know which of the possible `raise` statements was reached. 
@raises(ValueError) 
def test5(): 
    bar(10) 

# Yes, we're getting an exception but with the wrong value: bug found! 
def test6(): 
    with assert_raises_regexp(ValueError, "arg should be less than 10"): 
     bar(10) 

test5 che utilizza @raises passerà, ma passerà per la ragione sbagliata. test6 esegue un test più accurato che rivela che lo ValueError generato non era quello che volevamo.