Il capitolo 7 parla sostanzialmente di testing. L’autore, come ho già detto nel preambolo ha contribuito allo sviluppo di una applicazione IronPython che ha circa 40.000 righe e circa 150.000 di test…
I post precedenti sono: proprietà, dialog e Visual Studio, first class functions e xml, applicazioni e design pattern, oggetti .NET e IronPython, introduzione a Python, introduzione al libro e il preambolo.
Il default nel mondo Python per il testing unitario è il modulo unittest, modello sul framework JUnit che tutti conosciamo. Il suo funzionamento in parole povere: il runner identifica i metodi che cominciano con ‘test’, li esegue e si segna i fallimenti e gli errori. Esempio:
>>> import unittest
>>> class TCase(unittest.TestCase):
... def setUp(self): pass
... def tearDown(self): pass
... def testSomething(self):
... self.assertEquals(2, 2, "two it's not two")
...
>>> unittest.main()
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
unittest.main()
esegue il runner, i metodi setUp
e tearDown
vengono rispettivamente eseguiti prima e dopo i metodi di test. Si possono avere anche metodi di setup e teardown a livello di modulo volendo. Le asserzioni si verificano con i vari metodi assertXYZ. La linea di comando mostra un punto per i test passati, una ‘F’ per quelli falliti e una ‘E’ per quelli in errore. Ovviamente esiste anche più di una interfaccia grafica per il modulo unittest (più o meno integrate con i vari IDE).
Personalmente penso che ci siano framework più comodi e versatili di unittest. Alcuni come nose
che sono basati su discovery e una architettura a plugin (facilmente estensibili), altri come py.test
che di particolare hanno la possibilità di distribuire i test su N macchine.
Infine esistono tool come pyFIT
per il fitnesse testing, e altri ancora.
A questo punto l’autore introduce l’uso dei mock objects per testare l’applicazione Windows Forms che sta sviluppando nei vari capitoli.
Apro una parentesi dicendo che a me è personalmente capitato di dover fare TDD sviluppando una applicazione GUI facendo anche uso di mock objects e devo dire che è davvero interessante. Come mi è anche capitato di usare tool come Selenium per fare test funzionali di applicazioni web. Sono tool che probabilmente poca gente usa ma che meritano un po’ di investimento.
Il libro ora comincia a creare un oggetto mock per testare l’applicazione MultiDoc riorganizzando nel frattempo anche il codice in maniera più organica, separando i test dal resto del codice.
Tra le molteplici librerie disponibili per Python l’autore sceglie quella che si è scritto da solo (che tra l’altro è anche la più diffusa credo, e quella che vi consiglio nel caso siate interessati): Mock.
Un altra caratteristica molto usata nel testing, prerogativa dei linguaggi dinamici, è il monkey patching ovvero la possibilità di patchare a runtime gli oggetti. È talmente importante che personalmente mi ha “salvato il culo” due o tre volte quando ho trovato bug in librerie e non c’era tempo per aspettare la versione successiva.
Per comprendere appieno il monkey patching di un metodo in Python bisogna comprendere le regole di lookup di un metodo. In breve: una istruzione come obj.method()
cerca il metodo prima nell’istanza, obj
, poi nella classe e poi in tutti gli antenati di quella classe. Se si aggiunge un metodo a runtime ad una classe questa modifica è riflessa su tutte le istanze di tale classe, se invece il metodo viene aggiunto ad una singola istanza la modifica non si riflette affatto:
>>> class Foo(object): pass
...
>>> f = Foo()
>>> g = Foo()
>>> def method(self): return 'method called'
...
>>> Foo.method = method # add a method at runtime
>>> f.method() # both the instances have it
'method called'
>>> g.method()
'method called'
>>> del Foo.method # remove the method at runtime
>>> def method2(): return 'method called'
...
>>> f.method = method2 # add a method to the instance
>>> f.method() # f has it
'method called'
>>> g.method() # g doesn't
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'method'
>>>
Notare che method
ha self
come argomento, mentre method2
non ha nessun argomento. Questo perché il lookup in una classe crea un oggetto metodo bound a cui passare self
. In una istanza è semplicemente una funzione unbound.
Il libro dopo aver introdotto il concetto di monkey patching e le regole di lookup usa il primo concetto per patchare a runtime un metodo facendo in modo che ritorni un oggetto mock invece che l’oggetto reale. Molto semplice, una volta capito cosa c’è dietro. È una pratica molto comune.
Dopodiché l’autore introduce la dependency injection per diminuire l’accoppiamento tra il comportamento dei test e quello delle classi.
Il capitolo termina con una introduzione al testing funzionale e le user story. Per testare in maniera black box alcune feature dell’applicazione, come la creazione di un nuovo tab per una pagina, usa il multithreading e Invoke
per creare un mini framework per inviare pressioni di tasti, movimenti di mouse e pressione di bottoni all’applicazione da testare.