Testing en Python

Imagen de resetreboot

¿Que son los tests?

Los tests son funciones y código específicamente diseñado para probar funcionalidad de nuestro código y asegurarnos, en la medida de lo posible, que funcionan.

Especialmente interesante es su capacidad de detectar cuando, al hacer cambios, rompemos cosas que funcionan antes incluso de subir a nuestro master de nuestro gestor de versiones o entornos de prueba. A pesar de lo maravillosos que son, normalmente en el MundoReal™ los plazos no dan para hacer buenos tests (si llega para hacer tests, claro) o bien nos da pereza (total, nuestro código es estupendo, ¿verdad?)

Teoría básica de los tests

El testing consiste en, a grandes rasgos, crear situaciones hipotéticas, hacer pasar a nuestro código por ellas y comprobar que:

  1. No casca
  2. Se comporta como esperamos que lo haga

Supongamos que tenemos una función def suma(a, b): que suma a y b. Un test para esta función, llamaría a la función con los parámetros a=2 y b=2 y comprobaría que el resultado es 4, para, a continuación, probar que con a=2 y b=3 el resultado es 5 e incluso que vale lo mismo si a=3 y b=2.

Como se puede comprobar, hacer tests es un proceso de razonamiento por parte del programador, que requiere de buscarle las cosquillas a nuestro código, para que no se nos pase ningún caso. Sin embargo, la realidad es que los tests, aunque empiecen con buena cobertura, se van mejorando según los bugs asoman su fea cabeza, los aplastamos y añadimos el caso a nuestros tests para que no se vuelva a repetir.

Conceptos de testing

Un poco de terminología para entender este mundillo:

  • Fixture
    Es la preparación del entorno necesaria para realizar un test, ya sea carga de una base de datos de pruebas, lanzamiento de servicios externos o situar los componentes en determinado estado. Normalmente se prepara para uno o varios tests a la vez.
  • Test Case
    Es un test, un caso. Una de las unidades que prueban algo.
  • Suite de tests
    Es un conjunto de tests. Normalmente, irá pareado con los módulos de tu programa.
  • Runner
    Es la parte encargada de orquestar y lanzar los tests. Normalmente usaremos un framework o extenderemos uno.

Testing en Python

Ahora, tras la teoría, vamos al meollo de la cuestión.

Python nos provee de una librería llamada, en un alarde de originalidad, unittest que es el que usaremos para escribir nuestros tests. Pesos pesados como Django, extienden esta librería para sus frameworks de tests, con lo que nos conviene saber cómo funciona.

Imaginemos que vamos a testear nuestra librería de funciones matemáticas "mates", así que creamos un fichero que llamaremos como queramos, en este caso "mates_testing.py" y lo añadiremos como otro módulo python en nuestro código.

import mates
import unittest
 
class TestMates(unittest.TestCase):
    def setUp(self):
        mates.initTrigonometry()
 
    def test_suma(self):
        self.assertEqual(suma(2, 2), 4)
        self.assertEqual(suma(2, 3), 5)
        self.assertEqual(suma(2, 3), suma(2, 3))

La clase que hemos creado, engloba varios tests (aunque en este caso. sólo tengamos uno), hereda de unittest.TestCase y lo que hacemos es crear métodos que empiezan con test en el nombre. Esto es importante, ya que es cómo el código de testeo detectará lo que son tests y los ejecutará, ignorando el resto. Esto permite tener dentro de nuestra clase métodos que llamaremos nosotros cuando necesitemos, y que no sean ejecutados como tests.

El método setUp es lo primero que se ejecutará siempre, antes de ir lanzando los tests uno tras otro, es donde prepararemos los fixtures y lanzaremos la inicializaciones de nuestro código. Aquí podemos crear conexiones a la base de datos, crear nuevas bases de datos, generar cosas, o, como en este caso, inicializar algo que tiene que ver con la trigonometría.

Assert

Los asserts son la parte más importante de nuestro código de test. Estas funciones permiten comprobar que una condición determinada se cumple. En nuestro ejemplo, self.assertEqual compara que ambos parámetros son iguales, y en caso de no serlo, avisa al runner para que marque el fallo y lo notifique.

Tipos de assert

  • assertEqual(a, b): Comprueba que a == b
  • assertNotEqual(a, b): Comprueba que a != b
  • assertTrue(x): Comprueba que x evalúa a True
  • assertFalse(x): Comprueba que x evalúa a False
  • assertIs(a, b): Comprueba "a is b" (que a y b evalúan al mismo objeto)
  • assertIsNot(a, b): Comprueba que a y b no evalúan al mismo objeto
  • assertIsNone(x): Comprueba que x es None
  • assertIsNotNone(x): Comprueba que x NO es None
  • assertIn(a, b): Comprueba que a está en b
  • assertNotIn(a, b): Comprueba que a no está en b
  • assertIsInstance(a, b): Comprueba que a es instancia de b (con isinstance)
  • assertNotIsInstance(a, b): Comprueba que a no es instancia de b (con isinstance)

También existen para comprobar que una función con ciertos parámetros lanza un tipo de Exception determinado, pero dejamos eso para revisar en la documentación oficial.

Por último, aunque en el ejemplo no lo hemos puesto, existe el método tearDown() que se ejecutará al finalizar los tests y sólo si setUp no ha dado problemas. Sirve para hacer limpieza tras los tests, eliminando bases de datos de prueba o liberando recursos.

Ejecutar tests

Para lanzar nuestros tests, vamos a instalar el framework "nose". Si estáis usando virtualenv (y si no, ¿a qué estáis esperando?) podéis instalarlo con pip install nose. Si no, podéis instalarlo con vuestro gestor de paquetes favorito.

Nose se va a encargar de localizar, ejecutar, recopilar resultados y darnos una salida bonita y clara de la ejecución de todos nuestros tests.

Una vez instalado, ejecutando nosetest en la consola, en nuestro directorio de proyecto o código, él hará todo el trabajo sucio. Si todo va bien y nuestro código (y los tests) son buenos, tendremos una salida tal que así:

..................................
----------------------------------------------------------------------
Ran 34 tests in 1.440s
 
OK

que viene a decir que todo ha salido estupendamente. Si algún test falla, lo marcará con una F y dirá qué assert es el que ha fallado. Si tenemos una E, es que ha saltado alguna excepción sin controlar y nos pondrá el traceback por pantalla. Finalmente, un resumen con todos los fallos y errores encontrados.

Con esto, tras hacer nuestros cambios al código, podremos ver de un vistazo rápido, que no hemos roto otra cosa aparte.

Nose tiene muchas ventajas como runner de nuestros tests. Aparte de hacer la vida más sencilla a la hora de encontrar y ejecutar todos nuestros tests aunque seamos muy desordenados, tiene un buen montón de plugins de terceros que nos pueden ayudar a que la tarea de testear nuestro código, sea más llevadera.

Despedida y cierre

En resumen, de esto trata todo esto: De ser paranoicos e hijoputas con nuestro código en los tests, buscándole las cosquillas para ver dónde puede fallar, para que cuando salga al salvaje MundoReal™ tenga mayores posibilidades de sobrevivir sin que pete. Si tenéis dudas o algo que añadir, los comentarios son siempre bienvenidos

Happy testing!

6 comments

4
Mar

Imagen de elautoestopista

Pues me ha aclarado muchas cosas, que me estoy iniciando tanto en la programación en serio como en Python.
¿Por qué no aparece en portada?

Lista de Correo #BOFHers - http://www.freelists.org/list/bofhers
Síguenos también el Twitter, en el hastag #BOFHers (y derivados)
¡También tenemos un servidor IRC! irc.bofhers.com:6667 canal #BOFHers

5
Mar

Imagen de resetreboot

Pues creo que no aparece en portada por que en vez de Artículo, escogí "Tips". No se si hay alguna forma de subirlo.

Y me alegro de que te haya resultado útil.

7
Mar

Imagen de vfmBOFH

Hay un ticker en la página de edición, sección "Opciones de publicación" que pone bien clarito "Promovido a portada".

Si haces una entrada de blog va a portada directo, creo. Pero si son páginas de libro o así, hay que promoverlo.

Atentamente, La voz ésa del interior de tu cabeza que oyes cuando lees algo.

17
Mar

Imagen de resetreboot

Ahhh, míralo tú... Pues nada, despistes de uno. Tengo que postear más a menudo.

8
Abr

Imagen de PhobosDBM

Puntualmente, cuando el tiempo lo permite, hago algunas pruebas con JUnit (lo mismo que ha explicado resetreboot, pero para Java), aunque también he de decir que como las manos de un consultor psicópata con mala leche y ganas de petarte Redmine no hay nada.

Pero supongo que dependerá de cada empresa. En mi caso, prima más la prisa por sacar cosas sin terminar que ceder tiempo para hacer las cosas debidamente.

18
Ene

moncler jackets
canada goose
coach factory outlet
christian louboutin outlet
ugg boots
canada goose jackets uk
yeezy 350 boost
uggs outlet
ray ban sunglasses discount
coach factorty outlet
michael kors outlet
michael kors outlet
pandora charms
michael kors outlet online
canada goose outlet
moncler jackets
coach factory outlet online
canada goose sale
kate spade
polo outlet
coach outlet
hermens bags
pandora jewelry
canada goose outlet
ugg boots
moncler outlet
nike zoom
moncler jackets outlet
the north face
longchamp outlet
coach factory outlet
timberland outlet
coach factory outlet
ralph lauren outlet
michael kors outlet
cheap uggs
adidas yeezy
north face jackets
canada goose outlet
coach outlet online
louis vuitton outlet
cheap ray ban sunglasses
ralph lauren outlet
mulberry uk
cheap ugg boots
nike shoes
hermes handbags
ugg outlet
oakley sunglasses
ugg shoes
ugg outlet store
coach outlet
fitflops sale clearance
jordan retro 11
adidas nmd
moncler outlet
burberry
cheap nfl jerseys
pandora jewelry
pandora jewelry
nike outlet
air max 97
ugg boots
michael kors canada
canada goose jackets
coach factory outlet
christian louboutin outlet
birkenstocks
ugg boots
ralph lauren uk
coach handbags
ugg outlet
nike outlet
north face outlet online
michael kors outlet
coach outlet online
oakley sunglasses
michael kors outlet
burberry outlet store
ugg shoes
coach factorty outlet
moncler outlet
coach canada
coach factory outlet online
cheap jordans for sale
pandora outlet
air max 2018
ralph lauren outlet
ugg boots
nike shoes
ugg ustralia
michael kors outlet store
air jordan shoes
cheap ray bans
nike outlet store
michael kors outlet
ralph lauren sale clearance
michael kors handbags
harden vol 1
uggs outlet
michael kors outlet clearance
nike outlet online
timberland boots
ugg boots
fitflops
coach outlet
kate spade
adidas yeezy boost
pandora charms uk
coach factory outlet
ralph lauren outlet
nike outlet store
yeezy boost
uggs clearance
supreme clothing
michael kors outlet
cheap mlb jerseys china
coach canada
cheap mlb jerseys
nike store
ugg boots canada
pandora charms sale clearance
canada goose
pandora jewelry outlet
nike air max
adidas superstar
cheap uggs
uggs
yeezy boost
ugg outlet
nike shoes
ultra boost
adidas nmd
coach outlet
supreme clothing
moncler outlet
north face outlet
ugg boots
coach outlet
michael kors outlet clearance
canada goose jackets
fitflops
mbt shoes
oakley sunglasses
jordan retro
oakley sunglasses
coach outlet
canada goose
ray ban sunglasses
moncler jackets
pandora
cheap jordans
ferragamo shoes
adidas yeezy
moncler uk
adidas outlet
moncler outlet
north face jackets
adidas nmd
ugg outlet
canadian goose
nmd shoes
coach outlet store
canada goose outlet
michael kors outlet
ralph lauren outlet
adidas nmd
cat boots
air jordan shoes
canada goose
red bottom shoes
canada goose outlet
ralph lauren
louis vuitton
michael kors outlet clearance
polo ralph lauren outlet online
burberry outlet
polo ralph lauren outlet
mbt
air jordans
ugg outlet
canada goose clothing
ralph lauren outlet
coach outlet
yeezy boost
ray ban sunglasses
pandora charms
canada goose outlet
uggs canada
canada goose jackets
kate spade outlet store
cheap nfl jerseys
ralph lauren outlet
canada goose outlet
pandora charms sale clearance
michael kors handbags
hermes handbags
canada goose coats
canada goose uk
canada goose
north face jackets
pandora charms
longchamp uk
cheap jordans
uggs
hermes bags
coach factory outlet
canada goose uk
cheap jordans
ralph lauren outlet
michael kors outlet
salvatore ferragamo shoes
oakley sunglasses
uggs
kate spade outlet store
supreme clothing
ugg outlet
coach outlet
doudoune moncler
ralph lauren
longchamp outlet
columbia sportswear
coach outlet
pandora jewelry
canada goose uk
coach factory outlet
burberry outlet
cheap uggs
christian louboutin
canada goose outlet
ralph lauren outlet
ugg boots
nike shoes for men
ugg outlet
coach outlet store online clearances
pandora charms
canada goose outlet
ugg boots on sale
michael kors outlet
ugg boots for women
ralph lauren outlet
moncler jackets
nfl jerseys wholesale
canada goose outlet
coach outlet
mbt shoes
kate spade outlet
coach factory outlet
oakley sunglasses
cheap jordans
ugg boots
jordan shoes
pandora outlet
pandora charms sale clearance
coach factory outlet
timberland boots
pandora store
canada goose uk
coach outlet
canada goose sale
fred perry polo
air max 97
coach outlet store
polo ralph lauren
michael kors outlet
coach outlet
burberry outlet
yeezy boost
discount oakley sunglasses
pandora charms
canada goose outlet
adidas shoes
uggs canada
michael kors outlet
adidas outlet
coach outlet store
ugg outlet
canada goose sale
ugg outlet
moncler jackets
oakley sunglasses sale
coach factory outlet
pandora charms sale
kate spade handbags
louis vuitton outlet store
canada goose jackets
valentino shoes outlet
adidas yeezy boost
christian louboutin outlet
polo ralph lauren outlet online
polo ralph lauren outlet
burberry sale
kate spade outlet
canada goose outlet
pandora charms sale
coach outlet store
michael kors outlet clearance
adidas shoes
nmd adidas
pandora outlet
coach outlet
adidas yeezy
coach outlet store
mulberry handbags
michael kors
kate spade bags
cheap jordans
moncler coats
hermes birkin
jordans
kate spade outlet
michael kors outlet
coach outlet
ralph lauren uk
red bottoms shoes
adidas outlet
polo ralph lauren outlet
nmd adidas
fitflops sale clearance
polo ralph lauren outlet online
michael kors outlet clearance
ralph lauren uk
mulberry outlet
pandora charms
ugg outlet
louis vuitton outlet online
louboutin shoes
cheap jordans free shipping
tory burch outlet
ralph lauren sale clearance
adidas campus
coach factory outlet
canada goose uk
canada goose jackets
longchamp outlet online
supreme new york
fitflops sale
longchamp handbags
red bottoms
canada goose sale
ralph lauren outlet
coach outlet
coach factory outlet
north face jackets
philipp plein outlet
gucci outlet
nfl jerseys wholesale
canada goose jackets
coach bags
coach outlet store online
air jordans
polo outlet
canada goose outlet
ugg outlet
canada goose
ugg boots
michael kors handbags
canada goose outlet
ugg outlet
polo ralph lauren outlet online
yeezy boost 350
ugg australia
coach outlet
valentino shoes
tory burch outlet store
pandora charms
ralph lauren outlet
ferragamo outlet
canada goose
nike air max
moncler
cheap oakley sunglasses
adidas shoes
ugg outlet
longchamp handbags
jordan shoes
polo ralph lauren outlet online
ralph lauren outlet
coach outlet
ugg boots on sale
fred perry
michael kors outlet
cheap ray ban sunglasses
ugg outlet online
canada goose outlet
moncler outlet
adidas yeezy
adidas shoes
burberry outlet canada
ray ban sunglasses
ugg boots
moncler
adidas yeezy
canada goose jackets
nike outlet
michael kors uk
mulberry bags
ugg outlet
ugg boots
michael kors canada
north face outlet
louboutin shoes
hermes handbags
louis vuitton outlet
cheap nfl jerseys
ray ban sunglasses
christian louboutin
cheap oakley sunglasses
coach outlet
ugg outlet
canada goose outlet
jordan shoes
adidas superstar
pandora charms
michael kors outlet
michael kors outlet
canada goose jackets
birkenstock sandals
canada goose outlet
ugg outlet
gucci handbags
adidas yeezy boost
pandora jewelry
michael kors outlet online
coach outlet
polo ralph lauren outlet
michael kors factory outlet
fitflops
air max 2018
coach outlet
timberland boots outlet
uggs outlet
philipp plein shirt
ugg outlet
michael kors outlet online
polo ralph lauren outlet
adidas
ugg boots
louis vuitton outlet online
ralph lauren uk
ugg outlet
hermes outlet
north face outlet
air max 2017
kate spade handbag
ugg outlet
cheap ray ban sunglasses
coach outlet
salvatore ferragamo
ray ban sunglasses
coach factory outlet
adidas shoes
michael kors outlet store
yeezy boost
1.03linpingping