13 de Diciembre, 2010

Tests en Python y Coverage

No es que sea un creyente del TDD, porque no me convence escribir el test antes de programar una nueva caracterísica, pero en general sí me parece adecuado escribir pruebas para todo, como herramienta principal para llevar un control de calidad.

Asegurarnos de que un software funciona como debe es en general difícil: hay tener en cuenta todos los casos posibles para que todo quede perfectamente probado. Incluso a veces hasta es complicado saber cuánto hemos probado.

Para mejorar la situación podemos trabajar con Coverage, que es una herramienta que permite analizar la ejecución de nuestros programas en Python, indicándonos qué partes se ejecutan y qué partes no.

Esto puede ser muy útil como herramienta de control cuando estamos programando nuestros tests, y además como un cierto indicador de calidad en el proyecto: ejecutamos nuestros tests con Coverage, y podemos ver qué cantidad del código ha sido probada efectivamente.

Vamos a analizar una pequeña función (para el caso: Insertion Sort):

def isort(a):
    for j in range(1, len(a)):
        k = a[j]
        i = j - 1
        while (i >= 0) and (a[i] > k):
            a[i+1] = a[i]
            i = i - 1
            a[i+1] = k
    return a

def main():
    r = isort([1, 2, 3, 4, 5])
    print r

if __name__=="__main__":
    main()

Es realmente secillo ver a simple vista que el vector que usamos de prueba no es el mejore posible ;), pero como ejemplo nos va a valer. Primero ejecutamos el programa bajo en control de coverage:

$ coverage run code.py
[1, 2, 3, 4, 5]

Efectivamente, maravilla de las maravillas, nos devuelve el vector ordenado. Aquí pararíamos, satisfechos de que nuestro código está probado :D.

Pero vamos a asegurarnos. Lo siguiente es pedir a Coverage un informe (la ejecución anterior nos ha dejado un fichero .coverage en el directorio de trabajo, que se usará en el informe):

$ coverage report -m code.py 
Name    Stmts   Exec  Cover   Missing
-------------------------------------
code       14     12    85%   7-8

Vaya, nuestra prueba inicial solo ha cubierto el 85% de nuestro código :(.

El siguiente paso sería ver qué código no se ha ejecutado:

$ coverage annotate code.py

Eso generará un fichero code.py,cover como el siguiente:

> def isort(a):
> 	for j in range(1, len(a)):
> 		k = a[j]
> 		i = j - 1
> 		while (i > 0) and (a[i] > k):
! 			a[i+1] = a[i]
! 			i = i - 1
> 		a[i+1] = k
> 	return a
  
> def main():
> 	r = isort([1, 2, 3, 4, 5])
> 	print r
  
> if __name__=="__main__":
> 	main()

Coverage nos ha indicado con las exclamaciones el código que no se ha ejecutado (resaltado en amarillo).

Nuestro ejemplo es muy sencillo, solo hay una bifurcación posible, y bastará con que no indiquemos un vector ya ordenado para que se ejecute todo el código, pero en aplicaciones con lógica compleja... este informe puede resultar de gran ayuda.

El hecho de que nuestros tests cubran todo nuestro código no implica que sean buenos tests, ni que nuestra aplicacion esté bien probada. Además, conseguir probar el 100% de nuestro código puede ser realmente complicado a veces, y eso tampoco quiere decir que los tests no cubran bien los casos importantes.

Coverage me parece una herramienta excelente cuando estoy programando nuevos test, facilitanto mucho las cosas, y hasta es un poco adictiva (¡toooma! cover 100% :P).

Anotación por Juan J. Martínez, clasificada en: python, programming.

Los comentarios están cerrados: los comentarios se cierran automáticamente una vez pasados 30 días. Si quieres comentar algo acerca de la anotación, puedes hacerlo por e-mail.

Algunas anotaciones relacionadas: