Documentar y probar sus scripts en Python
Introducción
A partir de ahora, escribir pruebas unitarias es inevitable en la elaboración de un programa informático. A este respecto, Python se entrega con módulos opcionales que responden a las expectativas de los desarrolladores más ambiciosos. Python también le ofrece la posibilidad de inspeccionar su código de manera interactiva con REPL y comprobar instantáneamente el contenido de un objeto, su tipo y los métodos que ofrece. Para ayudar al desarrollador en las tareas relacionadas con la documentación, el análisis de rendimiento y la resolución de problemas en lo que respecta al código, la gama de módulos ofrecidos por el lenguaje es muy amplia. Por ejemplo, cuando el tamaño de un proyecto se hace crítico, el uso de pruebas unitarias permite implementar más rápidamente nuevas funcionalidades y detectar las regresiones de código desde el inicio de la implementación. Esto hace que se gane tiempo y productividad. La búsqueda de un buen rendimiento va a ayudar al desarrollador a identificar las funciones que consumen muchos recursos durante la ejecución y aquellas que consumen menos, con el objetivo de refactorizar y/o reescribir el código. En caso de escribir scripts destinados a la Raspberry Pi, donde los recursos son restringidos, auditar y hacer procesos de benchmarking sobre su código puede mejorar el tiempo...
Consultar la documentación con pydoc3
En primer lugar, la herramienta más habitual que permite buscar en la documentación de los módulos se llama pydoc3. El uso de pydoc3 interviene cuando queremos mostrar la documentación de un módulo, clase o palabra clave. Por ejemplo, la documentación de todas las palabras clave revisadas en los capítulos anteriores (with, def, lambda, etc.) se puede consultar con pydoc3. Un aspecto importante a observar antes de ir más lejos: la mayoría de la documentación instalada con el sistema por defecto de Python está escrita en inglés.
Esta herramienta se utiliza exclusivamente en línea de comandos. Abra una consola pulsando Menú - Accesorios - LXTerminal, como se explicó en el capítulo Entorno de programación. A continuación, escriba el siguiente comando:
pi@raspberrypi:~ $ pydoc3
Debería mostrar el siguiente resultado:
Esto solo es la ayuda de pydoc3. Sin embargo, preste atención porque cada versión de Python instalada en el sistema se entrega con su propia versión de pydoc. Como se explica en el capítulo Entorno de programación, diferentes versiones del compilador Python deben convivir en la Raspberry Pi. Ocurre igual para pydoc, que se entrega en sus versiones 2.7 y 3.4. Por defecto, el binario pydoc apunta a /usr/bin/pydoc2.7. Por lo tanto, piense siempre en utilizar pydoc3 para leer la documentación de la versión 3 de Python.
De este modo, pydoc3 permite buscar y mostrar la documentación de numerosos topics o temas. El tema buscado se corresponde con el término que se ha pasado como argumento del comando. En este ejemplo, se busca el término with y se muestra la documentación...
Documentar y probar su código al mismo tiempo, con el módulo doctest
La documentación del código es una etapa esencial durante la elaboración de un programa Python. En la empresa y en numerosos proyectos open source, documentar el código es primordial, porque este se distribuye a un gran número de desarrolladores. Es importante que el código sea claro y comprensible y una buena documentación facilita mucho esta etapa. En Python, escribir documentación requiere utilizar docstrings. Los diseñadores del lenguaje consideran los docstrings tan importantes que hay incluso una PEP que explica los estándares para escribirlos. PEP 0257 - Docstring Conventions, disponible en la dirección https://www.python.org/dev/peps/pep-0257/.
Para entender mejor para qué sirve un docstring, es útil escribir un módulo que contenga dos funciones, dos_veces y cinco_veces, que multiplique respectivamente un entero por 2 o por 5 y devuelva el resultado. Hasta ahora, los ejemplos que habíamos escrito no estaban documentados, con el objetivo de optimizar el espacio y los caracteres. Aquí, el módulo se documenta totalmente (Capitulo_4/doctest_1.py):
1 #!/usr/bin/env python3
2
3 """ Ejemplo del libro 'Desarrollar con Python en Raspberry
Pi'.
4
5 Este archivo lista algunas operaciones aritméticas básicas.
6 """
7
8 def int_dos_veces(x):
9 """
10 Multiplica la variable x por 2 y devuelve el resultado.
11
12 @param: x, la variable a multiplicar.
13 @return: un entero.
14 """
15 return int(x * 2)
16
17 def int_cinco_veces(x):
18 """
19 Multiplica la variable x por 5 y devuelve el resultado.
20
21 @param: x, la variable a multiplicar.
22 @return: un entero.
23 """
24 return int(x * 5)
Una docstring es fundamentalmente una cadena de caracteres. Empieza por tres comillas, también llamadas dobles...
Escritura de pruebas unitarias con el módulo unittest
Habitualmente llamado PyUnit, el módulo unittest es una librería que forma parte de la librería estándar desde la versión 2.1 del lenguaje. Prácticamente inevitables, las pruebas unitarias están muy extendidas en el mundo del desarrollo de software para la validación del código y representan casi una disciplina en sí misma. Este capítulo se concentra en la presentación de la librería, dejando de lado sin embargo la metodología que hay que adoptar cuando se escriben pruebas unitarias. Hay muchas y frente a necesidades en permanente evolución de las empresas es difícil aconsejar una u otra metodología: ¿Es mejor Agile?, ¿TDD? , ¿Waterflow?
El primer ejemplo de esta serie tiene el mismo código que el utilizado para explicar el módulo doctest (Capitulo_4/unittest_1.py):
1 #!/usr/bin/env python3
2 import unittest
3
4 def int_dos_veces(x):
5 return int(x * 2)
6
7 def int_cinco_veces(x):
8 return int(x * 5)
9
10 class MyUnitTests(unittest.TestCase):
11 def testEquals(self):
12 self.assertEqual(int_dos_veces(2), 4)
13 self.assertEqual(int_dos_veces(6), 12)
14 self.assertEqual(int_cinco_veces(5), 25)
15 self.assertEqual(int_cinco_veces(10), 50)
16
17 def testNotEquals(self):
18 self.assertNotEqual(int_dos_veces(5), 1)
19 self.assertNotEqual(int_dos_veces(15), 1)
20 self.assertNotEqual(int_cinco_veces(12), 1)
21 self.assertNotEqual(int_cinco_veces(52), 1)
22
23 if __name__ == '__main__':
24 unittest.main()
En particular, la escritura de pruebas unitarias debe respetar una cierta lógica;...
Hacer procesos de benchmarking sobre su código con el módulo timeit
Para los desarrolladores que quieran explotar al máximo los recursos de Python, de la Raspberry Pi y de su código, es posible medir el tiempo de ejecución de un programa con el módulo timeit. Saber cuánto tiempo necesita un programa para ejecutarse y, sobre todo, identificar en qué punto de un programa se necesitan más recursos, puede permitir en algunas ocasiones ganar algunos segundos preciosos.
timeit se puede utilizar de dos maneras: directamente desde la línea de comandos o cargándolo desde un script. Aunque el uso en línea de comandos pueda resultar atractivo a primera vista, es conveniente para los scripts one liners pero está poco adaptado cuando el código a probar se extiende en varias líneas.
En realidad, el módulo no mide el tiempo de ejecución de un programa totalmente, sino el tiempo de ejecución de un bloque de código en particular. Es recomendable utilizar timeit en pequeños bloques y concentrarse en lo que está más condensado en términos de código: una función.
Para demostrar cómo mejorar el tiempo de ejecución de una función, vamos a reutilizar una función de este capítulo, la función int_dos_veces() del módulo doctest_1.py que vamos a mejorar. Como recordatorio, a continuación se muestra la definición de la función:
1 def int_dos_veces(x):
2 """
3 Multiplica la variable x por 2 y devuelve el resultado.
4
5 @param: x, la variable a multiplicar.
6 @return: un entero.
7 """
8 return int(x * 2)
Con el objetivo de cronometrar el tiempo de ejecución de esta función, un script debe importar el módulo doctest_1.py. El benchmarking se desarrolla de la siguiente manera (Capitulo_4/timeit_1.py):
1 #!/usr/bin/env python3 ...
Depurar sus programas con el módulo pdb
Finalmente, ¿qué hacer cuando el programa no se comporta como estaba previsto? La solución más sencilla consiste en depurar el programa, es decir, cambiar a una consola que permita ejecutar el programa, aislar cada llamada de función, etapa por etapa y preguntar por el contenido de cada variable, hasta llegar a la llamada o a la variable que presenta el problema. Para instanciar un depurador en Python, hay que cargar el módulo pdb, que significa Python DeBugger.
El uso del depurador es muy sencillo: se puede hacer en línea de comandos o directamente en el programa que queremos depurar.
Hay varias maneras de depurar un programa y vamos a explicar la manera más larga, con el objetivo de aprovecharla para explicar las funcionalidades que el módulo pdb ofrece al desarrollador.
El programa que sirve de ejemplo y para depurar provoca una excepción dentro de un bucle for, y en particular cuando el índice del bucle es igual a 3. A continuación se muestra el código fuente del programa (Capitulo_4/pdb_1.py):
1 #!/usr/bin/env python3
2
3 def explosion(arg='kaboom!', elem=1):
4 elem = elem + 1
5 return elem / 0
6
7 for i in range(5):
8 if i == 3:
9 explosion(elem=i)
1. Depurar paso a paso
Para lanzar el programa desde el depurador, es necesario invocar a python3 con la opción -m, seguida del nombre del módulo correspondiente, aquí pdb:
pi@raspberrypi:~/Desarrollar_con_Python_en_Raspberry_Pi/Capitulo_4
$ python3 -m pdb pdb_1.py
> /home/pi/Desarrollar_con_Python_en_Raspberry_Pi/Capitulo_4/pdb_1.py
(3)<module>()
-> def divide(arg='kaboom!', elem=1):
(Pdb) _
El depurador se inicializa, arranca el programa y se detiene en la primera definición de código que encuentra. En un programa, la definición de una función se considera como una ejecución de código y se interpreta antes incluso de que empiece a ejecutarse.
En cualquier momento, pdb puede mostrar un resumen del desarrollo del proceso de la pila, así como la línea que se está interpretando. Para mostrar esto, basta con pulsar list o bien...
Conclusión
Este capítulo explica cómo documentar, probar, depurar y medir el rendimiento de su código a través del uso de cuatro módulos, que son doctest, unittest, pdb y timeit. También se ha revisado la herramienta estándar para leer la documentación en Python, pydoc3.