Python nose: scrivere test senza fatica per il nostro codice

Presto o tardi chi scrive codice deve fare i conti con imprevisti!
Uno dei modi per evitare sorprese e' quello di testare il codice scrivendo dei test che ne confermino la validita' di funzionamento.

Chi scrive codice in Python potra' giovarsi dalle comodita' offerte da Python Nose per scrivere test senza grossa fatica. La particolarita' di Python Nose e' che riesce a scovare le funzioni e le classi di test nel codice basandosi sul loro nome e senza bisogno di importare moduli esterni.

Per esempio...

Supposto che abbiate installato nose, andiamo a cimentarci con una funzione molto semplice ma che puo' riservare sorprese!
Guardate qua:
def fattoriale(n):
"""Funzione ricorsiva per il calcolo del fattoriale di un mnumero
"""
if n == 0:
return 1
else:
return fattoriale(n-1)*n
Che ne pensate? A prima vista sembra scritta bene, ma in realta' riserva qualche sorpresa.
Prima di tutto siamo sicuri funzioni?

Per esserne certi salviamo questa funzione in un file con estensione ".py" e aggiungiamo ad essa una prima funzione di validazione che controlli che i risultati siano corretti.
Affinche' nose si accorga della funzione di test, il suo nome dovra' contenere il prefisso "test" (geniale! :)). Il concetto e' che il test avra' successo la funzione ritornera' dei valori coerenti con quelli che sono calcolabili per altra via. Ad esempio i fattoriali dei numeri 0,1,2,3,4,5 corrispondono ai valori 1,1,2,6,24 e 120.
E' ovvio che se troviamo risultati diversi da questi significa che qualcosa nella funzione non torna.

La funzione che fa il test assomigliera' a questa:
def test_fattoriale():
"""Funzione invocata per testare la correttezza nel calcolo
"""
checks = (1,1,2,6,25,120)
for i, check in enumerate(checks):
f = fattoriale(i)
assert f == check,\
"Il fattoriale di %s e' %s" % (f, check)
Come vedete definiamo nella variabile checks i valori "corretti" e li confrontiamo con quelli che andremo a calcolare con la nostra fantastica funzione!

Il vostro file, fatto questo, assomigliera' a questo:

#!/usr/bin/env python
def fattoriale(n):
"""Funzione ricorsiva per il calcolo del fattoriale di un mnumero
"""
if n == 0:
return 1
else:
return fattoriale(n-1)*n

def test_fattoriale():
"""Funzione invocata per testare la correttezza nel calcolo
"""
checks = (1,1,2,6,25,120)
for i, check in enumerate(checks):
f = fattoriale(i)
assert f == check,\
"Il fattoriale di %s e' %s" % (f, check)
E' arrivato il momento della verita'!!!
Per lanciare il test date il comando nosetest seguito dal nome del file, ad es.:
nosetests prova.py

A questo punto dovreste scoprire che il test e' miseramente fallito!
Il motivo di questo non sta nella funzione bensi' nel test, che e' scritto male... Infatti come ci dice l'output si aspettava che il fattoriale di 4 fosse 25 (non 24 come si puo' ottenere moltiplicando i numeri interi da uno a quattro e come giustamente sostiene la funzione scritta).

Correggiamo la tupla di controllo, sostituendo 25 con 24 e rilanciamo il test. Questa volta tutto dovrebbe filare liscio.

Aggiungiamo test meno banali
Come avete visto la funziona supera brillantemente il test. Pero' fallira' quest'altro test:
def test_tipo():
"""Controlla che la funzione fattoriale ritorni un numero di tipo long
"""
for i in (0,1,2,10,200):
assert type(fattoriale(i)) == long ,\
"Il fattoriale di %s non e' di tipo long!" % i

Questo potrebbe rappresentare un problema in certi casi!
Per far passare alla funzione il test occorre modificarla in questo modo:
#!/usr/bin/env python
def fattoriale(n):
"""Funzione ricorsiva per il calcolo del fattoriale di un mnumero
"""
if n == 0:
return 1L
else:
return fattoriale(n-1)*n

Come vedete nel caso n sia 0 ritorna 1L, (prima ritornava semplicemente 1) ovvero un intero di tipo long. Questo forzera' il tipo del risultato ritornato dalle altre iterazioni, facendoci passare il test!

Fattoriale di un numero molto alto
La nostra funzione non e' ancora a prova di bomba. Facciamola scoppiare!
def test_boom():
"""Controlliamo che la funzione reagisca bene quando si trova a che fare
con numeri grandi
"""
fattoriale(1e10)
Per fare in modo che la funzione gestisca questo test occorrera' gestire l'eccezione che viene sollevata nel caso in cui il numero di cui si vuole calcolare il fattoriale sia troppo alto.

Nessun commento: