Tests de funcions. Doctest¶
Començarem suposant que hem de resoldre el següent problema per utilitzar-lo com a exemple:
Dissenya la funció doble que, donat un nombre enter positiu, retorna
el doble d'aquest nombre.
Executem IDLE, obrim un fitxer nou i escrivim allà la següent funció:
def calcula_doble(n):
return 2*n
Desem la funció a un fitxer amb el nom doble.py.
Execució manual de tests¶
La primera forma d’executar tests per les nostres funcions és fer-lo manualment. Suposant que ja tenim la funció programada i desada a un fitxer, utilitzarem l’opció Run Module per importar la funció al shell. Un cop importada, la provarem simplement teclejant crides amb diferents arguments i comprovant si el resultat és l’esperat.
>>> calcula_doble(0)
0
>>> calcula_doble(3)
6
>>> calcula_doble(143)
286
Execució de proves amb doctest¶
Què és doctest?¶
doctest és un mòdul de Python per executar proves de codi basades en exemples de com s’ha de comportar una funció donades unes entrades determinades. Permet executar automàticament un conjunt de tests guardats a un fitxer. L’objectiu és no haver d’escriure manualment al shell totes les proves cada vegada que fem alguna modificació a la funció.
Els fitxers de tests¶
Un fitxer de tests per a doctest replica el format d’una sessió interactiva amb el shell de Python. És com si s’hagués teclejat al fitxer tots els exemples que es provarien manualment al shell. Cada exemple que volem provar (una crida a la funció amb uns arguments concrets) comença amb >>>, seguit de la línia de codi que volem executar. A la línia següent, s’especifica el resultat esperat.
Com que el fitxer de tests no s’executa en el mateix entorn interactiu que el shell, és necessari importar prèviament la funció o mòdul que volem testejar. Això es fa incloent les línies d’importació al principi del fitxer de test. Si no s’importa la funció correcta, doctest no podrà executar els exemples correctament. Per això és important que sempre es respectin els noms de funcions i fitxers demanats als exercicis.
Per exemple, si volguéssim incloure els mateixos tests que hem provat abans manualment, tindríem el següent fitxer de test:
>>> from doble import calcula_doble
>>> calcula_doble(0)
0
>>> calcula_doble(3)
6
>>> calcula_doble(143)
286
doctest executarà automàticament cada exemple i compararà el resultat obtingut amb el resultat esperat. Si el resultat no coincideix amb l’esperat, doctest mostrarà un error indicant on va fallar la prova.
Warning
No hauràs d’escriure fitxers de test, estaran disponibles amb els enunciats de cada problema. Però sí que has d’entendre què contenen i com s’utilitzen.
Com executar un fitxer de test amb doctest des del terminal¶
Per executar un fitxer de test amb doctest des del terminal hem de tenir un terminal obert i tenir desats en el mateix directori (carpeta) el fitxer amb la funció que es vol testejar (al nostre exemple, el fitxer doble.py) i el fitxer amb els tests (al nostre exemple, el fitxer test-doble.txt).
Pots descarregar el fitxer de test anterior test-doble.txt
Per executar el mòdul doctest i indicar-li que volem que executi els exemples d’un fitxer, farem servir la següent comanda al terminal:
python3 -m doctest nom_fitxer_de_test -v -f
Pel nostre exemple, la comanda seria
python3 -m doctest test-doble.txt -v -f
El nom del fitxer de test és el nom del fitxer amb tests que hem descarregat des de la pàgina de l’exercici que estem resolent, no és el nom del fitxer que conté la funció.
L’opció
-vfa que, encara que no hi hagi errors, Python mostri missatges amb el resultat i sigui més clar.L’opció
-ffa quedoctestpari quan trobi el primer error. És molt útil perquè els errors s’han de corregir sempre en ordre, començant pel primer i pot fer-se difícil de trobar si s’executen tots els tests i n’hi ha moltes línies de resultats a pantalla.
Després de l’execució d’aquesta comanda, obtindrem un resultat com el següent:
Trying:
from doble import calcula_doble
Expecting nothing
ok
Trying:
calcula_doble(0)
Expecting:
0
ok
Trying:
calcula_doble(3)
Expecting:
6
ok
Trying:
calcula_doble(143)
Expecting:
286
ok
1 items passed all tests:
4 tests in doble_test.txt
4 tests in 1 items.
4 passed and 0 failed.
Test passed.
TestResults(failed=0, attempted=4)
S’han executat automàticament cadascun dels test especificats per la funció. En aquest cas, la funció ha passat tots els tests però, si n’hi hagués errors, de sintaxi o de que els resultats no són correctes, estarien indicats. Si s’ha de corregir la funció, cada vegada que es fa una modificació s’han de desar els canvis i executar després la comanda indicada anteriorment per executar doctest.
Warning
Els tests són exemples de resultats correctes per comprovar si una funció és correcta i a on falla si no ho és. No els utilitzis per “calcular la nota” d’un exercici mirant quants surten correctes i quants no, perquè doctest compta com a tests totes les línies que comencen per >>>, però algunes poden contenir assignacions de variables o càlculs necessaris per comprovar els resultats que poden comptar com a correctes tot i que la funció no ho sigui.
Com executar un fitxer de test amb doctest des d’IDLE¶
Per executar un fitxer de test amb doctest des d’IDLE hem de tenir desats en el mateix directori (carpeta) el fitxer amb la funció que es vol testejar (al nostre exemple, el fitxer doble.py) i el fitxer amb els tests (al nostre exemple, el fitxer test-doble.txt).
Pots descarregar el fitxer de test anterior test-doble.txt
Amb el fitxer que conté la funció obert a la finestra d’edició d’IDLE (al nostre exemple, el fitxer doble.py, executem Run Module per importar la funció al shell. Això és important per garantir que IDLE estableix com directori de treball el directori a on es troba el fitxer amb la funció.
Ara dins del shell, s’ha d’importar el mòdul doctest i executar el fitxer de test de la següent forma:
>>> import doctest
>>> doctest.testfile('test-doble.txt',verbose=True)
El que va entre cometes és sempre el nom del fitxer de test, que descarregarem des de la pàgina de l’exercici que estem resolent, no és el nom del fitxer que conté la funció.
Utilitzant doctest des d’IDLE,
doctestno para quan troba el primer error, com es pot fer utilitzant-lo des del terminal, i es mostren tots els resultats.
Els resultats seran els mateixos que quan executem doctest des del terminal, però dins del shell d’IDLE.
Tests amb objectes de tipus float¶
Començarem suposant que hem de resoldre el següent problema per utilitzar-lo com a exemple:
Dissenya la funció area_triangle que donats la base i l'altura d'un triangle retorna l'àrea.
Executem IDLE, obrim un fitxer nou, escrivim allà la següent funció i la desem al fitxer area_triangle.py:
def area_triangle(base,altura):
return base*altura/2
Suposem també que tenim el següent fitxer de tests que podem descarregar al fitxer test-atriangle.txt:
>>> from area_triangle import area_triangle
>>> area_triangle(3, 4)
6.0
>>> area_triangle(12, 5)
30.0
>>> area_triangle(3,1.52)
2.28
Problemes amb la precisió d’objectes float amb doctest¶
Comprovem que la funció passa tots els tests amb la comanda següent:
python3 -m doctest atriangle.txt -v -f
Ens donarà el següent resultat:
Trying:
from area_triangle import area_triangle
Expecting nothing
ok
Trying:
area_triangle(3, 4)
Expecting:
6.0
ok
Trying:
area_triangle(12, 5)
Expecting:
30.0
ok
Trying:
area_triangle(3,1.52)
Expecting:
2.28
**********************************************************************
File "test-atriangle.txt", line 7, in test-atriangle.txt
Failed example:
area_triangle(3,1.52)
Expected:
2.28
Got:
2.2800000000000002
**********************************************************************
1 items had failures:
1 of 4 in test-atriangle.txt
4 tests in 1 items.
3 passed and 1 failed.
***Test Failed*** 1 failures.
Tot i que el resultat 2.28 especificat al test per la crida area_triangle(3, 1.52) és correcte, el test falla. El motiu és que la funció retorna 2.2800000000000002, una petita desviació deguda a errors de precisió en la representació dels nombres de tipus float. Aquest tipus d’errors són habituals i no indiquen que la funció estigui mal dissenyada.
Per evitar que aquests desajustos de precisió facin fallar tests que, en realitat, són correctes, podem fer servir la funció round() per arrodonir el resultat abans de comparar-lo, limitant-lo a un nombre fix de decimals. El test quedaria així:
>>> round(area_triangle(3,1.52),2)
2.28
Pots descarregar el nou fitxer de tests corregit test-atriangle-corregit.txt. Un cop utilitzem aquest test actualitzat, la funció supera totes les proves correctament.