Testing

Al fine di garantire per quanto possibile, il corretto funzionamento e la qualità del software si possono usare diversi tipi di prove, come ad esempio le prove unitarie (unit test), le prove di integrazione o le prove di regressione.
In questo capitolo ci concentriamo sulle prove unitarie o unit test, dal quale si valida il corretto funzionamento delle unità logiche in cui è suddiviso il programma, indipendentemente dal rapporto con le altre unità.
La soluzione più estesa per le prove unitarie nel mondo Python è unittest, spesso in combinazione con doctest per semplici test. Entrambi i moduli sono inclusi nella libreria standard di Python.
Doctest
Come previsto dal nome del modulo, doctest permette di combinare le prove con la documentazione. Questa idea di utilizzare unit test per testare il codice e anche come documentazione, consente di realizzare dei test con estrema facilità, favorisce la manutenzione dei test e serve per dare una indicazione di utilizzo del codice e per aiutare a capire il suo scopo.
Quando doctest trova una linea nella documentazione che inizia con ‘>>>’ presuppone che ciò che segue è il codice Python da eseguire, e che la risposta attesa è la seguente linea o le linee senza ‘>>>’. Il testo della prova termina quando c’è una riga vuota, o quando raggiunge la fine della stringa di documentazione.
Si consideri la seguente funzione, che restituisce un elenco dei quadrati di tutti i numeri che compongono l’elenco passato come parametro:
			def quadrato(lista):
			    """Calcola il quadrato dei numeri da un elenco"""
			
			    return [n ** 2 for n in lista]
Si potrebbe creare un test come questo, dove verifichiamo che il risultato passando [0, 1, 2, 3] è quello previsto:
			def quadrato(lista):
			    """Calcola il quadrato dei numeri da un elenco
			
			    >>> l = [0, 1, 2, 3]
			    >>> quadrato(l)
			    [0, 1, 4, 9]
			    """
			    return [n ** 2 for n in lista]
Ciò che facciamo in questo esempio è indicare a doctest di creare una lista l con il valore [0, 1, 2, 3], che chiami di seguito la funzione quadrato con l come argomento, e di verificare che il risultato restituito sia pari a [0, 1, 4, 9].
Per eseguire i tests si utilizza la funzione testmod del modulo, al quale si potrà passare opzionalmente il nome del modulo da valutare (parametro name). Nel caso in cui no si indichi nessun argomento, come in questo caso, viene valutato il modulo corrente:
			def quadrato(lista):
			    """Calcola il quadrato dei numeri da un elenco
			
			    >>> l = [0, 1, 2, 3]
			    >>> quadrato(l)
			    [0, 1, 4, 9]
			    """
			
			    return [n ** 2 for n in lista]
			
			def _test():
			    import doctest
			    doctest.testmod()
			
			if __name__ == "__main__":
			    _test()
Nel caso in cui il codice non passa alcune delle prove che abbiamo definito, doctest mostrerà il risultato ottenuto e il risultato previsto. In caso contrario, in caso di successo, non visualizzerà alcun messaggio se non si aggiunge l’opzione -v quando chiameremo lo script o il parametro verbose =True alla funzione tesmod, nel qual caso si mostrerà tutte le prove effettuate, a prescindere se hanno avuto successo o meno.
Questo sarebbe l’aspetto dell’uscita del doctest utilizzando il parametro -v:
			Trying:
			    l = [0, 1, 2, 3]
			Expecting nothing
			ok
			Trying:
			    quadrato(l)
			Expecting:
			    [0, 1, 4, 9]
			ok
			2 items had no tests:
			__main__
			        __main__._test
			1 items passed all tests:
			    2 tests in __main__.quadrato
			2 tests in 3 items.
			2 passed and 0 failed.
			Test passed.
Ora introdurremo un errore nella funzione del codice per vedere l’aspetto di un messaggio di errore di doctest. Supponiamo, per esempio, di avere scritto un operatore di moltiplicazione (‘*‘) al posto di un elevamento a potenza (‘**‘):
			def quadrato(lista):
			    """Calcola il quadrato dei numeri da un elenco
			    >>> l = [0, 1, 2, 3]
			    >>> quadrato(l)
			    [0, 1, 4, 9]
			    """
			
			    return [n * 2 for n in lista]
			
			def _test():
			    import doctest
			    doctest.testmod()
			
			if __name__ == "__main__":
			    _test()
Otteniamo qualcosa come questo:
			*********************************************************
			File "esempio.py", line 5, in __main__.quadrato
			Failed example:
			quadrato(l)
			Expected:
			    [0, 1, 4, 9]
			Got:
			    [0, 2, 4, 6]
			*********************************************************
			1 items had failures:
			    1 of 2 in __main__.cuadrados
			***Test Failed*** 1 failures.
Come si vede, il messaggio ci indica che ha fallito il test sulla linea 5, al chiamare quadrato(l), il cui risultato dovrebbe essere [0, 1, 4, 9], ma abbiamo ottenuto [0, 2, 4, 6].
Vediamo infine come utilizzare le dichiarazioni nidificate per rendere le cose un po’ più complicate con doctest. Nell’esempio che segue la nostra funzione calcola il quadrato di un singolo numero passato come parametro, e disegniamo un test per verificare che il risultato sia quello giusto con multipli chiamate e con diversi valori. Le dichiarazioni annidate iniziano con “” anziché con “>>>“:
			def quadrato(num):
			    """Calcola il quadrato da un numero.
			    >>> l = [0, 1, 2, 3]
			    >>> for n in l:
			    ... cuadrado(n)
			    [0, 1, 4, 9]
			    """
			
			    return num ** 2
			
			def _test():
			    import doctest
			    doctest.testmod()
			
			if __name__ == "__main__":
			    _test()
unittest/PyUnit
unittest, chiamato anche PyUnit, fa’ parte di una famiglia di strumenti conosciuti collettivamente come xUnit, un insieme di frameworks basati sul software SUnit per Smalltalk, creato da Kent Beck, uno dei padri di eXtreme Programming. Altri esempi di strumenti che fanno parte di questa famiglia sono JUnit per Java, creato dallo stesso Kent Beck insieme a Erich Gamma, o NUnit per .NET.
L’uso di unittest è molto semplice. Per ogni gruppo di test dobbiamo creare una classe che erediti unittest.TestCase, e poi aggiungere un certo numero di metodi che inizino con test, che diventeranno ognuno dei test che vogliamo eseguire in questa batteria di test.
Per eseguire i tests, è sufficiente chiamare la funzione main() del modulo, con il quale si eseguirano tutti i metodi il cui nome inizia con test, in ordine alfanumerico. Quando si esegue ognuno dei test il risultatopuò essere:

OK: Il test è stato superato con successo.
FAIL: Il test non ha avuto successo. Si lancia un’eccezione AssertionError per indicano.
ERROR: All’eseguire il test si è generato un’eccezione diversa da AssertionError.

Nell’esempio seguente, dato che il nostro metodo che modella il nostro test non lancia alcuna eccezione, il test passerebbe con successo.

			import unittest
			
			class EsempioProve(unittest.TestCase):
			    def test(self):
			        pass
			
			if __name__ == "__main__":
			    unittest.main()
In quest’altro, però, fallirebbe:
			import unittest
			
			class EsempioProve(unittest.TestCase):
			    def test(self):
			        raise AssertionError()
			
			if __name__ == "__main__":
			    unittest.main()
Nulla ci impedisce di utilizzare le clausole if per valutare le condizioni che ci interessano e lanciare un’eccezione del tipo AssertionError quando non è così; ma la classe TestCase dispone di diversi metodi che possono facilitare il compito per semplici test. Questi sono:

assertAlmostEqual(first, second, places=7, msg=None): Verifica che gli oggetti passati come parametri siano uguali fino alla settima cifra decimale (o il numero di cifre decimali indicati da places).
assertEqual(first, second, msg=None): Verifica che gli oggetti passati come parametri siano uguali.
assertFalse(expr, msg=None): Verifica che l’espressione sia falsa.
assertNotAlmostEqual(first, second, places=7, msg=None): Verifica che gli oggetti passati come parametri non siano uguali fino alla settima cifra decimale (o il numero di cifre decimali indicati da places).
assertNotEqual(first, second, msg=None): Verifica che gli oggetti passati come parametri non siano uguali.
assertRaises(excClass, callableObj, *args, **kwargs): Verifica che al chiamare l’oggetto callableObj con i parametri definiti da *args e **kwargs lanci un’eccezione di tipo excClass.
assertTrue(expr, msg=None): Verifica che l’espressione sia vera.
assert_(expr, msg=None): Verifica che l’espressione sia vera.
fail(msg=None): Fallisce immediatamente.
failIf(expr, msg=None): Fallisce se l’espressione è vera.
failIfAlmostEqual(first, second, places=7, msg=None): Fallisce se oggetti passati come parametri sono uguali fino alla settima cifra decimale (o il numero di cifre decimali indicati da places).
failIfEqual(first, second, msg=None): Fallisce se gli oggetti passati come parametri sono uguali.
failUnless(expr, msg=None): Fallisce a meno che l’espressione sia vera.
failUnlessAlmostEqual(first, second, places=7, msg=None): Fallisce a meno che gli oggetti passati come parametri sono uguali fino alla settima cifra decimale (o il numero di cifre decimali indicati da places).
failUnlessEqual(first, second, msg=None): Fallisce a meno che gli oggetti passati come parametri siano uguali.
failUnlessRaises(excClass, callableObj, *args, **kwargs): Fallisce a meno che al chiamare l’oggetto callableObj con i parametri definiti da *args e **kwargs lanci un’eccezione di tipo excClass.

Come vediamo tutti i metodi hanno un parametro opzionale msg con un messaggio da visualizzare quando questo controllo ha esito negativo.
Torniamo alla nostra piccola funzione per calcolare il quadrato di un numero. Per testare le prestazioni della funzione possiamo fare, ad esempio, così:

			import unittest
			
			def quadrato(num):
			    """Calcola il quadrato da un numero."""
			    return num ** 2
			
			class EsempioProve(unittest.TestCase):
			    def test(self):
			        l = [0, 1, 2, 3]
			        r = [quadrato(n) for n in l]
			        self.assertEqual(r, [0, 1, 4, 9])
			        
			if __name__ == "__main__":
			
			unittest.main()
Preparazione del contesto
A volte è necessario preparare l’ambiente in cui vogliamo eseguire il test. Ad esempio, potrebbe essere necessario inserire dei valori predefiniti in un database, creare una connessione a una macchina, creare qualche file, ecc. Questo è ciò che si conosce nel mondo xUnit come test fixture.
La classe TestCase fornisce un paio di metodi che possiamo sovrescrivere per costruire e decostruire l’ambiente e che si eseguiranno prima e dopo le prove definite in quella classe. Questi metodi sono setUp() e tearDown().
				class EsempioFixture(unittest.TestCase):
				    def setUp(self):
				        print "Preparando contesto"
				        self.lista = [0, 1, 2, 3]
				
				    def test(self):
				        print "Eseguendo il test"
				        r = [quadrato(n) for n in self.lista]
				        self.assertEqual(r, [0, 1, 4, 9])
				
				    def tearDown(self):
				        print "Decostruendo il contesto"
				        del self.lista
				


Similari
Overloading di metodi in Java
12% Java
Un metodo overload viene utilizzato per riutilizzare il nome di un metodo ma con argomenti diversi, opzionalmente con un differente tipo di ritorno. [expand title=”Regole per overload” startwrap=”” endwrap=”” excerpt=”⤽” s…
Modi di fare e di non fare in Python
11% Python
Questo documento può essere considerato un compagno del tutorial di Python. Viene illustrato come utilizzare Python, e quasi ancora più importante, come non usare Python. [expand title=”Costrutti del linguaggio che non dov…
redirect 301 usando mod_alias
9% Server
mod_alias è fondamentalmente la versione più semplice di mod_rewrite. Non può fare le cose che fa mod_rewrite, ad esempio modificare la stringa di query. Per eseguire reindirizzamenti nel server web Apache è possibile di u…
Installare Python e Django su Windows
8% Django
Quando ci riferiamo allo sviluppo web con Python, la prima cosa che viene in mente è usare un qualche framework. Il più famoso e utilizzato da tutti è il Django, ma non è l’unico. Ci sono Pylons, Grok, TurboGears e Zope: t…
Metodi magici e costanti predefinite in PHP
8% Php
PHP fornisce un insieme di costanti predefinite e metodi magici per i nostri programmi. A differenza delle normali costanti i quali si impostano con define(), il valore delle costanti predefinite o speciali dipendono da do…