2012-03-22 9 views
10

Questo è un problema che si presentava quando si eseguiva un singolo test con più modalità di errore indipendenti, a causa della presenza di più flussi di uscita. Volevo anche mostrare i risultati dell'affermazione dei dati su tutte quelle modalità, indipendentemente da quale errore si sia verificato prima. L'unittest di Python non ha questa caratteristica al di fuori dell'uso di una Suite per rappresentare il singolo test, il che era inaccettabile dal momento che il mio singolo test aveva sempre bisogno di essere eseguito come una singola unità; semplicemente non cattura la natura della cosa.Come posso gestire più asserti all'interno di un singolo unipest di Python?

Un esempio pratico è la verifica di un oggetto che genera anche un registro. Vuoi dichiarare l'output dei suoi metodi, ma vuoi anche dichiarare l'output del registro. Le due uscite richiedono test diversi, che possono essere espressi in modo chiaro come due delle espressioni asserzioni azionarie, ma non si vuole anche che il fallimento di uno impedisca l'eventuale fallimento dell'altro all'interno del test. Quindi hai davvero bisogno di testare entrambi allo stesso tempo.

Ho messo insieme questo piccolo widget utile per risolvere il mio problema.

def logFailures(fnList): 
    failurelog = [] 
    for fn in fnList: 
     try: 
      fn() 
     except AssertionError as e: 
      failurelog.append("\nFailure %d: %s" % (len(failurelog)+1,str(e))) 

    if len(failurelog) != 0: 
     raise AssertionError(
      "%d failures within test.\n %s" % (len(failurelog),"\n".join(failurelog)) 
     ) 

che viene usato in questo modo:

def test__myTest(): 
    # do some work here 
    logFailures([ 
     lambda: assert_(False,"This test failed."), 
     lambda: assert_(False,"This test also failed."), 
    ]) 

Il risultato è che logFailures() solleverà un'eccezione che contiene un registro di tutte le affermazioni che sono state sollevate nei metodi all'interno della lista.

La domanda: Mentre questo fa il lavoro, mi chiedo se c'è un modo migliore per gestire questo, oltre a dover fare il tempo di creare suite di test nidificati e così via?

+3

"anche tu non vuoi il fallimento di uno per nascondere il possibile fallimento dell'altro all'interno del test". Se vuoi testare due cose diverse, rendi due test differenti! –

+1

"anche tu non vuoi il fallimento di uno per nascondere il possibile fallimento dell'altro all'interno del test". Sì, voglio: questi sono test unitari. Se un test fallisce, correggere l'errore e rieseguire i test. –

risposta

12

Non sono d'accordo con l'opinione dominante che si dovrebbe scrivere un metodo di prova per ciascuna affermazione. Ci sono situazioni in cui si desidera controllare più cose in un unico metodo di prova. Ecco la mia risposta per come farlo:

# Works with unittest in Python 2.7 
class ExpectingTestCase(unittest.TestCase): 
    def run(self, result=None): 
     self._result = result 
     self._num_expectations = 0 
     super(ExpectingTestCase, self).run(result) 

    def _fail(self, failure): 
     try: 
      raise failure 
     except failure.__class__: 
      self._result.addFailure(self, sys.exc_info()) 

    def expect_true(self, a, msg): 
     if not a: 
      self._fail(self.failureException(msg)) 
     self._num_expectations += 1 

    def expect_equal(self, a, b, msg=''): 
     if a != b: 
      msg = '({}) Expected {} to equal {}. '.format(self._num_expectations, a, b) + msg 
      self._fail(self.failureException(msg)) 
     self._num_expectations += 1 

e qui ci sono alcune situazioni in cui penso che sia utile e non rischioso:

1) Quando si desidera verificare il codice per diversi insiemi di dati. Qui abbiamo una funzione add() e voglio testarla con alcuni esempi di input. Scrivere 3 metodi di test per i 3 set di dati significa ripetere te stesso che è male. Soprattutto se la chiamata è stata più elaborata .:

class MyTest(ExpectingTestCase): 
    def test_multiple_inputs(self): 
     for a, b, expect in ([1,1,2], [0,0,0], [2,2,4]): 
      self.expect_equal(expect, add(a,b), 'inputs: {} {}'.format(a,b)) 

2) Quando si desidera controllare più uscite di una funzione. Voglio controllare ogni uscita ma non voglio che un primo errore mascherino gli altri due.

class MyTest(ExpectingTestCase): 
    def test_things_with_no_side_effects(self): 
     a, b, c = myfunc() 
     self.expect_equal('first value', a) 
     self.expect_equal('second value', b) 
     self.expect_equal('third value', c) 

3) Verificare le cose con costi di installazione pesanti. I test devono essere eseguiti rapidamente o le persone smettono di usarli. Alcuni test richiedono una connessione db o di rete che richiede un secondo, il che rallenterebbe davvero il test. Se stai testando la connessione db stessa, probabilmente dovrai prendere il colpo di velocità. Ma se stai testando qualcosa di non correlato, vogliamo eseguire l'installazione lenta una volta per un intero set di controlli.

+1

Qualcosa di simile dovrebbe essere presente nei framework di test unitari per impostazione predefinita. Qualcuno ne conosce uno che ha questa funzionalità? – pmos

10

Mi sembra una cosa troppo ingegneristica. O:

  • Utilizzare due asserzioni in un caso di test. Se la prima affermazione fallisce, è vero, non saprai se la seconda affermazione è passata o meno. Ma dovrai comunque correggere il codice, quindi correggilo e poi scoprirai se è passata la seconda affermazione.

  • Scrivere due test, uno per verificare ciascuna condizione. Se temi il codice duplicato nei test, inserisci la maggior parte del codice in un metodo di supporto che chiami dai test.

3

Con utilizzando un test secondario, l'esecuzione non si ferma dopo il primo fallimento https://docs.python.org/3/library/unittest.html#subtests

Ecco esempio con due sicuro afferma:

class TestMultipleAsserts(unittest.TestCase): 

    def test_multipleasserts(self): 
     with self.subTest(): 
      self.assertEqual(1, 0) 
     with self.subTest(): 
      self.assertEqual(2, 0) 

uscita sarà:

====================================================================== 
FAIL: test_multipleasserts (__main__.TestMultipleAsserts) (<subtest>) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "./test.py", line 9, in test_multipleasserts 
    self.assertEqual(1, 0) 
AssertionError: 1 != 0 

====================================================================== 
FAIL: test_multipleasserts (__main__.TestMultipleAsserts) (<subtest>) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "./test.py", line 11, in test_multipleasserts 
    self.assertEqual(2, 0) 
AssertionError: 2 != 0 

---------------------------------------------------------------------- 
Ran 1 test in 0.000s 

FAILED (failures=2) 

Puoi facilmente eseguire il sottotesto come segue

class MyTestCase(unittest.TestCase): 
    def expectEqual(self, first, second, msg=None): 
     with self.subTest(): 
      self.assertEqual(first, second, msg) 

class TestMA(MyTestCase): 
    def test_ma(self): 
     self.expectEqual(3, 0) 
     self.expectEqual(4, 0)