Ya estamos llegando al final de la guía Python!
En el capítulo de hoy, “atacaremos” con una técnica de programación indispensable, que nos dará una gran ventaja: evitar “romper” el código con la incorporación de uno nuevo y prevenir bugs. Y ¿de qué técnica hablamos? Unit Testing o Test Unitarios.

Test Unitarios

Los test unitarios representan un mecanismo indispensable, para probar el funcionamiento individual, de cada parte del código, previniendo que el agregado de nuevo código, haga fallar el existente.

Changelog del capítulo VII

A continuación un resumen de los cambios que veremos:

capitulo6/constantes.py      Nueva constante no pública

capitulo6/helpers.py         Agregados test con doctest 
                             nueva función para guardar archivos

capitulo6/presupuesto.py     Refactoring: guardar_presupuesto()
                             utiliza función genérica para guardar

capitulo6/recibo.py          Refactoring: guardar_recibo()
                             utiliza función genérica para guardar

capitulo6/run.py             refactoring: validación argumentos recibidos

capitulo6/test/              carpeta que contiene los test del capítulo

Hoy, centraremos nuestra atención, haciendo énfasis en los cambios realizados al archivo helpers.py. Comencemos!

doctest

doctest es un módulo nativo de Python, que busca en los comentarios de nuestro código, fragmentos que se vean como sesiones del intérprete interactivo de Python, y procede a ejecutar dichos fragmentos, verificando que resulten como se le ha indicado.

Esto significa, que importando el módulo doctest, éste, buscará en los comentarios de nuestro código, todo fragmento que represente al interprete interactivo, para luego ejecutarlo. Por ejemplo:

import doctest


def sumar_dos_numeros(a, b):
    """Suma dos números y retorna su resultado

    Argumentos:
    a -- primer sumando
    b -- segundo sumando

    Test:
    >>> sumar_dos_numeros(25, 10)
    35
    >>> sumar_dos_numeros(30, 20)
    50
    """
    resultado = a + b
    print a + b

if __name__ == "__main__":
    doctest.testmod()

Si vemos el texto debajo de “Test:”, luce como el intérprete interactivo.
Aquí estoy invocando a la función:

>>> sumar_dos_numeros(25, 10)

Aquí, estoy “simulando” el resultado que arrojaría en el intérprete interactivo. Esto, será interpretado por doctest, como “el resultado esperado”:

35

Y finalmente, verifico que el módulo esté siendo ejecutado como script (no importado), de ser así, doy la orden a doctest de ejecutar el script en modo “test”:

if __name__ == "__main__":
    doctest.testmod()

Colocando los “doctest” en un archivo independiente

En nuestro ejemplo (archivo helpers.py), sin embargo, nos encontramos con estas líneas:

74	if __name__ == "__main__":
75	    doctest.testfile('tests/helpers.txt')

En este caso, lo que estamos haciendo, es indicar a doctest, que nuestras pruebas se encuentran en un archivo a parte: tests/helpers.txt Si abrimos este archivo, podremos ver todos los test:

1	    >>> from helpers import leer_archivo, crear_archivo, mostrar_texto
2	
3	Probando leer_archivo()
4	
5	    >>> leer_archivo('')
6	    'Error'
7	
8	    >>> leer_archivo('tests/archivo_de_prueba.txt')
9	    'Archivo de Prueba\nHola Mundo\n'
10	
11	Probando crear_archivo()
12	
13	    >>> crear_archivo('', 'contenido')
14	    'Error'
15	    >>> crear_archivo('', '')
16	    'Error'
17	    >>> crear_archivo('tests/archivo_de_prueba.txt', '')
18	    'Error'
19	    >>> crear_archivo('tests/archivo_de_prueba.txt', 'Archivo de Prueba\nHola Mundo\n')
20	
21	
22	Probando mostrar_texto()
23	
24	    >>> mostrar_texto('Hola Mundo')
25	    Hola Mundo
26	    >>> mostrar_texto()
25	     

Si te fijas las líneas resaltadas en gris, podrás ver las llamadas a los métodos que estamos testeando. Mientras que las resaltadas en negro, simulan el resultando esperado.

El texto que no aparece resaltado, es interpretado como parte de los comentarios, exceptuando la línea 1, que se encarga de importar los métodos a ser testeados.

Ejecutando los test

Una vez que el código fuente cuenta con los correspondientes test, es hora de correrlos. Para ello, en la línea de comandos, escribiremos:

python nombre_del_modulo_a_testear.py -v

En nuestro caso, navegaremos hasta la carpeta capitulo6 y escribiremos

python helpers.py -v

Cuando ejecutemos los test, veremos una salida similar a la siguiente:

Trying:
    from helpers import leer_archivo, crear_archivo, mostrar_texto
Expecting nothing
ok
Trying:
    leer_archivo('')
Expecting:
    'Error'
ok
Trying:
    leer_archivo('tests/archivo_de_prueba.txt')
Expecting:
    'Archivo de Prueba\nHola Mundo\n'
ok
Trying:
    crear_archivo('', 'contenido')
Expecting:
    'Error'
ok
Trying:
    crear_archivo('', '')
Expecting:
    'Error'
ok
Trying:
    crear_archivo('tests/archivo_de_prueba.txt', '')
Expecting:
    'Error'
ok
Trying:
    crear_archivo('tests/archivo_de_prueba.txt', 'Archivo de Prueba\nHola Mundo\n')
Expecting nothing
ok
Trying:
    mostrar_texto('Hola Mundo')
Expecting:
    Hola Mundo
ok
Trying:
    mostrar_texto()
Expecting nothing
ok
1 items passed all tests:
   9 tests in helpers.txt
9 tests in 1 items.
9 passed and 0 failed.
Test passed.

En lo anterior, “Trying” nos describe el código que se está ejecutando, mientras que “Expecting”, el resultado esperado. Si todo sale bien, concluirá el bloque indicando “ok”.

Al final del test, se puede acceder al reporte completo:

1 items passed all tests:
   9 tests in helpers.txt
9 tests in 1 items.
9 passed and 0 failed.
Test passed.

Solución del desafío del capítulo anterior

En el segundo desafío del capítulo anterior, la propuesta era pensar como podría evitarse la redundancia de código, en los métodos encargados de guardar los recibos y los presupuestos en formato HTML.

Siguiendo la línea inicial, de convertir en “ayudantes genéricos” aquellos métodos sin relación directa con el compartamiento del objeto en sí mismo, se creó un helper, para guardar dichos archivos:

47	def crear_archivo(ruta, contenido):
48	    """crea un archivo en la ruta pasada con el contenido indicado
49	
50	    Argumentos:
51	    ruta -- ruta al archivo. Ej. carpeta/archivo.txt
52	    contenido -- template renderizado
53	
54	    """
55	    if not ruta or not contenido:
56	        return 'Error'
57	    else:
58	        archivo = open(ruta, 'w')
59	        archivo.write(contenido)
60	        archivo.close()

Anexo de material de lectura complementario

El desafío de hoy, no dependerá de “resolver” un problema ni de encontrar ningún tipo de solución puntual. El reto de hoy, consiste en desafiarte a ti mismo:

Nunca terminas de aprender y jamás es suficiente lo que te enseñan.

Programar, no se limita a conocer la sintaxis y funcionamiento de un lenguaje de programación. Siempre podrás comenzar por una guía de aprendizaje, para conocer los caminos que puedes seguir, para convertirte en un verdadero profesional en determinada materia. Pero eso, no debe significarlo todo.

Solo lograrás ser un experto, en el momento en el que descubras, que jamás es suficiente lo que conoces y que el conocimiento no te otorga sabiduría.

Tienes una alternativa: no limitar tus recursos al mero conocimiento. No acotar tu carrera profesional al conocer sobre un lenguaje de programación.

De tu voluntad, dependerá el nivel que alcances, y de tu pasión, el superarte cada día.

A continuación, encontrarás un listado de recurso, que no puedes dejar de leer. La gran mayoría se encuentran en español. Las identificadas como “indispensables”, te recomiendo que hagas todo lo posible por leerlas. Las “recomendadas”, significa que “sería una buena idea” leerlas. Y las “complementarias”, dependerán de tu curiosidad.

El camino correcto…

Recurre a la razón, para asimilar lo que lees. Pero recurre a tus emociones, para saber si lo que haces, realmente te apasiona. Solo así, sabrás que estás siguiendo el camino correcto.

¡Disfruta la lectura y apasiónate practicando!

Lectura indispensable (ES)

Guía de aprendizaje de Python por Guido van Rossum (creador de Python)
Excelente material de referencias básicas del lenguaje.

Inmersión en Python (Dive into Python) por Mark Pilgrim
Excelente material para conocer a fondo el lenguaje y su aplicación en la programación orientada a objetos.

Python no muerde por Roberto Alsina
Excelente libro para aprender a razonar más allá del lenguaje.

Lecturas recomendadas (en español)

Tutorial Django por @cibernatural
Una muy buena guía para entender el funcionamiento del framework Django, ideal para crear aplicaciones Python Web based.

Tutorial de PyGTK por John Finlay
Tutorial oficial de PyGTK, una suite de módulos Python para crear aplicaciones de escritorio con interfaces gráficas GTK.

Lecturas complementarias (en español)

Aprenda a Pensar como un Programador con Python por Allen Downey, Jeffrey Elkner y Chris Meyers
Un libro extenso, para leer con tiempo y calma. Muy buen material para utilizar como referencia del lenguaje. Los últimos capítulos, hacen mayor énfasis en la programación orientada a objetos.

Lecturas recomendadas (EN)

MySQLdb User’s Guide por Andy Dustman
Guía oficial de MySQLdb, interface para trabajar con bases de datos MySQL desde Python.

Python doctest por Guido Van Rossum
Documentación oficial de Python doctest

The PyQT Tutorial por ZetCode
Tutorial sobre PyQT para crear aplicaciones de escritorio con interfaces gráficas (un especial agradecimiento Jorge Courbis por pasarme el link)