Lawrence

Twist again
posts - 73, comments - 168, trackbacks - 37

domenica 4 gennaio 2009

IronPython In Action, testing dinamico

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.

posted @ lunedì 1 gennaio 0001 00:00 | Feedback (1) | Filed Under [ ironpython ]

IronPython In Action, proprietà, dialog e Visual Studio

Il capitolo 6 introduce le proprietà in Python e l’uso di Visual Studio per estendere una applicazione IronPython creando una class library.

I post precedenti sono: first class functions e xml, applicazioni e design pattern, oggetti .NET e IronPython, introduzione a Python, introduzione al libro e il preambolo.

L’idea dell’autore è quella di usare parzialmente l’observer pattern per mantenere sincronizzate le parti dell’applicazione. Per scatenare i vari side effect durante l’accesso degli attributi usa le proprietà.

In Python, come ho detto nell’introduzione, esistono vari protocolli che definiscono alcune key feature del linguaggio. Le proprietà non sono altro che una implementazione del descriptor protocol, un protocollo diffusissimo che permette il totale controllo sull’accesso agli attributi di un oggetto. Altre implementazioni sono i class method e gli static method.

Un solo meccanismo e varie implementazioni… Python è piuttosto chiaro come linguaggio.

La signature completa di una property è:

>>> name = property(fget=None, fset=None, fdel=None, doc=None)

dove fget è la funzione getter, fset è la funzione setter, fdel è la funzione che intercetta la cancellazione dell’attributo e doc è la docstring della proprietà. Esempio:

>>> class Foo(object):
...     def __init__(self, x):
...         self._x = x # _x is private
...     def _getX(self):
...         print "getting x"
...         return self._x
...     def _setX(self, value):
...         print "setting x"
...         self._x = value
...     def _delX(self):
...         print "Attempting to delete x"
...     x = property(_getX, _setX, _delX)
... 
>>> foo = Foo('foo')
>>> foo.x
getting x
'foo'
>>> foo.x = 'bar'
setting x
>>> del foo.x
Attempting to delete x

Apro una parentesi: le property in Python non sono usate estensivamente come in C# per via del fatto che l’ambiente non lo richiede. Solitamente si procede così: si espone un attributo pubblico e lo si usa tranquillamente, se poi questo ha bisogno di una logica dietro allora lo si incapsula in una property. Questo permette una totale trasparenza verso l’utilizzatore dell’oggetto che non si accorgerà neanche che l’attributo pubblico è diventato una property senza dunque rompere alcuna interfaccia.

Dopo aver implementato i document observer il libro passa ad introdurre l’uso di Visual Studio per sviluppare una dialog da inglobare in una class library, oltre ad una serie di comandi per completare l’applicazione.

Io ho provato personalmente a creare la libreria seguendo le istruzioni del libro ed usando Visual Studio Express 2008 ed importarla in Python e tutto ha funzionato perfettamente. IronPython è davvero facilmente estensibile:

>>> import clr
>>> clr.AddReference('RenameTabDialog')
>>> import RenameTabDialog
>>> dir(RenameTabDialoglog)
>>> dir(RenameTabDialog)
['Class1', 'RenameTabDialogBase']
>>> # RenameTabDialogBase is the dialog

RenameTabDialog è la dll contenente la form RenameTabDialogBase che poi gli esempi usano estendendola.

Il capitolo finisce introducendo anche la classe BinaryFormatter per serializzare una serie di risorse bitmap.

posted @ lunedì 1 gennaio 0001 00:00 | Feedback (1) | Filed Under [ ironpython ]

Powered by:
Powered By Subtext Powered By ASP.NET