1

Pruebas unitarias (unit test) con python

Las pruebas unitarias o "unit test" por su término en inglés, son una herramienta muy poderosa en el desarrollo de software, las pruebas unitarias desde mi muy personal punto de vista, nos ayudan a evitarnos fuertes dolores de cabeza y desvelos imprevistos resolviendo desafortunados problemas de ultimo minutos en nuestra versión de producción.

Pero ¿de qué van las pruebas unitarias?, la respuesta es muy simple, consta en programar pequeñas pruebas que validen una función de nuestro programa, es decir, si tenemos un método llamado "divide_entre_dos" el cual recibe un número entero y te devuelve ese número dividido entre dos, nuestra prueba unitaria debería validar que en todo momento nuestro método devuelve el resultado esperado al ingresarle el valor correcto. Se que este ejemplo es algo tonto, pero quise explicarlo de la manera más simple posible, veamos un mucho mejor ejemplo.

Una buena práctica al desarrollar pruebas de cualquier tipo, no solo unitarias, es utilizar la metodología "test driven development" o "TDD", esta metodología sugiere que para toda característica de nuestra aplicación debemos escribir una prueba que valide el comportamientos de esa característica, y debemos hacerlo antes de de escribir el código de dicha característica, ver que la prueba no pase, si nuestra prueba está bien hecha, esta debería pasar al escribir el código de la característica a probar, y solo en ese caso, algo más o menos como esto:

# Primero escribiremos una clase vacía a probar
class Foo(object):
   pass

# Después escribiremos el código de nuestra prueba
import unittest

class FooTestCase(unittest.TestCase):
# Instanciamos nuestro objeto foo antes de correr cada prueba
   def setUp(self):
       self.foo = Foo()

   def test_get_gravatar_url(self):
       result = self.foo.gravatar_url("example1@gmail.com")
       self.assertEquals(result, "https://www.gravatar.com/avatar/f3e820cc128ffde207328176830dff87")

if __name__ == "__main__":
   unittest.main()

El código anterior es un ejemplo de cómo debería verse una prueba unitaria siguiendo la metodología TDD, primero escribió una clase "Foo" vacía, esta clase será nuestro objetivo a probar, como única prueba en este caso, vamos a validar que dicha clase contenga un método llamado "get_gravatar", aquí quiero explicar brevemente que gravatar es un servicio gratuito para asociar una imagen "avatar" a un correo electrónico, la idea es que varios sitios web puedan asignar un avatar a nuestra cuenta dentro de su sitio usando sólo nuestro correo electrónico, y generar una url de gravatar para nuestra cuenta de correo es muy sencillo, solo tenemos que pasar nuestro correo escrito todo en minusculas por un algoritmo de hasheo "MD5", concatenamos el resultado de eso con la url raiz "https://www.gravatar.com/avatar/" y tenemos la url que apunta al avatar que creamos en el sitio de gravatar asociado a ese correo.

Explicando algo mas del código antes escrito, debemos importar la librería de python "unittest", y crear una clase que herede de la clase "unittest.TestCase" para hacer nuestras pruebas. Por estándar todas las clases de pruebas cuentan con dos métodos que se ejecutarán antes y después de correr cada prueba, estos métodos son "setUp" y "tearDown", que por convención de "unit test" conservan el nombre que se usa en la mayoría de los lenguajes y no un nombre más "pythonico" usando guiones bajos para separar las palabras, estos métodos se ejecutan cada uno antes y después de correr cada método de prueba de nuestra clase, por lo que debemos tomar en cuenta que cada prueba corre bajo su propio y único contexto. Como última nota sólo agrego que no es obligatorio declarar setUp o tearDown, solo lo hacemos en caso de ser necesario.

Para nuestro ejemplo instanciamos el objeto foo dentro de setUp para asegurarnos de que cada prueba se hará con una instancia nueva de foo, y que cada una usa valores limpios al ejecutarse, este comportamiento de las pruebas unitarias es muy importante para evitar arrojarnos falsos positivos en nuestros resultados. Y por último, en una prueba unitaria todo método dentro de la clase prueba cuyo nombre inicie con "test_" será considerado como una prueba, y se ejecutará como una, por lo que las pruebas son básicamente estos métodos dentro de la clase de pruebas, dentro de cada una de estas pruebas, debemos ejecutar métodos "assert", ellos se encargaran de hacer las debidas validaciones de la prueba, y existen un montón, en la documentación oficial de la librería de unittest podemos ver mas a detalles cuantos existen y para qué están pensados todos ellos, en el ejemplo solo usamos "self.assertEquals", que sirve para validar que dos valores sean iguales, de no serlo, la prueba no pasará y el error nos dirá que la prueba no paso porque esos dos valores que se pasan como parámetros no son iguales.

Hasta este punto es evidente que la prueba no pasará, principalmente por que el método "gravatar_url" no existe en la clase Foo, si corremos la prueba esta va a tronar.

La prueba la corremos ejecutando el archivo de python que creamos en la consola, esto es así para este caso, ya que al final del archivo declaramos que si se ejecuta el mismo directamente con python nos ejecute el método "unittest.main()", que ejecuta todas las pruebas unitarias declaradas, sin necesidad de hacer nada más que eso.

Para que nuestra prueba pase solo basta modificar la clase Foo para dejarla más o menos así:

import hashlib

class Foo(object):
   def gravatar_url(self, email):
       hash = hashlib.md5(email.lower()).hexdigest()
       return "https://www.gravatar.com/avatar/" + hash

Así de simple, solo importamos la librería hashlib de python y la usamos para hashear a md5 el email, lo concatenamos a la url raíz de gravatar, y devolvemos el valor esperado, si corremos de nuevo la prueba, ya no veremos errores, pues todas las pruebas habrían pasado sin problemas.

Para concluir solo basta decir que la idea de las pruebas unitarias no se limita a validar cada componente de nuestra aplicación por separado y de forma aislada, sino que también nos ayuda a definir cuál será el comportamiento deseado de ese requerimiento, y eso es una ayuda enorme cuando no estás muy seguro de cómo debes resolver algo, pues las pruebas te exigen que lo esté para poder hacerla. Yo personalmente no me atrevería a desarrollar un proyecto grande sin tener una herramienta que me ayude a estar seguro que cuando he modificado algo, o cuando he refactorizado algo, no se rompió nada sin darme cuenta, y lo más importante, que no me llamaran a las tres de la mañana para decirme que la aplicación no funciona… bueno, aun hay un montón de razones por las que eso puede pasar!, pero que no sea por que una línea inesperada en el código lo ha descompuesto todo.

Aquí dejo el link de la documentación oficial de la librería unittest de python, en mi proximo post hablare de mas herramientas para complementar el desarrollo de pruebas.

https://docs.python.org/2/library/unittest.html

Jesus Anaya - Programador de python y django con un gusto obsesivo por las series de television.

Comentarios

  1. avatar
    Michael Duarte dijo

    Genial, buen aporte.

Pública un comentario