Tests de funcions. Doctest =========================== Començarem suposant que hem de resoldre el següent problema per utilitzar-lo com a exemple: .. code-block:: text 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ó: .. literalinclude:: doble.py :language: python3 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. .. code:: python >>> calcula_doble(0) 0 >>> calcula_doble(3) 6 >>> calcula_doble(143) 286 Execució de proves amb :mod:`doctest` ------------------------------------- Què és :mod:`doctest`? ~~~~~~~~~~~~~~~~~~~~~~ :mod:`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 :mod:`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, :mod:`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: .. literalinclude:: test-doble.txt :language: python3 :mod:`doctest` *executarà automàticament cada exemple i compararà el resultat obtingut amb el resultat esperat*. Si el resultat no coincideix amb l'esperat, :mod:`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 :mod:`doctest` des del terminal ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Per executar un fitxer de test amb :mod:`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 :download:`test-doble.txt ` Per executar el mòdul :mod:`doctest` i indicar-li que volem que executi els exemples d'un fitxer, farem servir la següent comanda al terminal: .. code-block:: bash python3 -m doctest nom_fitxer_de_test -v -f Pel nostre exemple, la comanda seria .. code-block:: bash 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ó ``-v`` fa que, encara que no hi hagi errors, Python mostri missatges amb el resultat i sigui més clar. * L'opció ``-f`` fa que :mod:`doctest` pari 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: .. literalinclude:: resultats.txt :language: python3 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 :mod:`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è :mod:`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 :mod:`doctest` des d'IDLE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Per executar un fitxer de test amb :mod:`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 :download:`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 :mod:`doctest` i executar el fitxer de test de la següent forma: .. code:: python >>> 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, :mod:`doctest` no 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 :mod:`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: .. code-block:: text 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 :file:`area_triangle.py`: .. literalinclude:: area_triangle.py :language: python3 Suposem també que tenim el següent fitxer de tests que podem descarregar al fitxer :download:`test-atriangle.txt`: .. literalinclude:: test-atriangle.txt :language: python3 Problemes amb la precisió d'objectes float amb doctest ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Comprovem que la funció passa tots els tests amb la comanda següent: .. code-block:: pycon python3 -m doctest atriangle.txt -v -f Ens donarà el següent resultat: .. literalinclude:: resultats-floats.txt :language: python3 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ó :func:`round` per arrodonir el resultat abans de comparar-lo, limitant-lo a un nombre fix de decimals. El test quedaria així: .. code-block:: pycon >>> round(area_triangle(3,1.52),2) 2.28 Pots descarregar el nou fitxer de tests corregit :download:`test-atriangle-corregit.txt`. Un cop utilitzem aquest test actualitzat, la funció supera totes les proves correctament.