08 enero 2015

Herramientas de cobertura de tests en Python

Supongo que hay muchas metricas para saber como de efectivos son nuestras pruebas unitarias a la hora de cubrir todas las casuísticas posibles en nuestros desarrollos. En el futuro tengo pensado formalizar mis conocimientos en TDD, pero por ahora sólo estoy jugando con los conceptos por lo que sólo sigo una métrica muy simple: si mis test ejecutan todas las líneas de código de mi programa, entonces debo estar enfocando bien mis tests.

Pero ¿cómo saber si tus tests ejecutan todo tu código?. Conforme crece el tamaño de tu código, también lo hace la cantidad de tus tests, por lo que es fácil dejar por olvido un fragmento de código sin probar. Ahí es donde entran en juego las herramientas de cobertura de tests.

Esas herramientas hacen seguimiento de la ejecución de tus tests y toman nota de las líneas de código visitadas. De esa manera, tras la ejecución de los tests se pueden ver las estadísticas sobre qué porcentage del código dse prueba en realidad y cual no.

Vamos a ver dos maneras de analizar la cobertura de tests de tus proyectos: desde la consola y desde tu IDE.

En caso de estar en un equipo Ubuntu, deberíamos instalar el paquete "python3-coverage" para obtener la herremienta de consola:

dante@Camelot:~/project-directory$ sudo aptitude install python3-coverage


Una vez instalado, python3-covergae debe ser ejecutado para llamar a tus pruebas unitarias:

dante@Camelot:~/project-directory$ python3-coverage run --omit "/usr/*" ./run_tests.py

Utilizo el parámetro "--omit" para mantener la carpeta "/usr" fuera de mis informes de cobertura. Antes de usar ese parámetro, las librerías externas que mi programa iba llamando se incluían en el informe. Obviamente, no creo tests para las librerías externas dado que no toco su implementación, por lo que incluirlas en el informe de cobertura sólo haría al mismo más difícil de leer. El script "run_test.py" es el mismo que expliqué en mi artículo sobre unittest.

Supongamos que todos nuestros tests se ejecutan correctamente. Se puede generar un informe de cobertura tanto en html como en xml. Para generar un informe en formato html, sólo hay que
ejecutar:

dante@Camelot:~/project-directory$ python3-coverage html


Este comendo genera una carpeta llamada "htmlcov" donde el se guarda el informe generado en html (su página de entrada es index.html). Lo único un poco molesto es que la carpeta htmlcov debe ser vorrada manualmente antes de generar un nuevo informe. Además, es bastante aburrido tener que buscar el fichero index.html para pichar en él y abrir el informe. Esa es la razón por la que prefiero usar un script que automatiza todas esas aburridas tareas.

#!/usr/bin/env bash

python3-coverage run --omit "/usr/*" ./run_tests.py
echo "Removing previous reports..."
rm -rf htmlcov/*
rmdir htmlcov
echo "Removed."
echo "Building coverage report..."
python3-coverage html
echo "Coverage report built."
echo "Let's show report."
(firefox htmlcov/index.html &> /dev/null &)
echo "Bye!"

Al ejecutar ese script (lo suelo llamar "run_tests_and_coverage.sh") consigo que el navegador Firefox se habra mostrando el informe de cobertura recién creado.

Si usas un IDE, lo más probable es que ya incluya algún tipo de herramienta de análisis de cobertura. PyCharm incluye un analizador de cobertura en su versión profesional. En realidad, con PyCharm se consigue más o menos lo mismo que con las herramientas de consola que vimos antes, pero  de manera integrada con el editor y por tanto mucho más cómoda de utilizar.

A nivel de aplicación, la configuración por defecto debería ser suficiente:



Supongo que si no se tiene instalado el paquete de sistema "python3-coverage" se debería marcar la opción "Use bundled coverage.py" para usar así la herramienta que trae integrada de manera nativa PyCharm. En mi caso, no he notado diferencia alguna en marcar o desmarcar dicha opción (aunque obviamente yo ya tengo el paquete "python3-coverage" instalado).

El único truco a recordar es que ejecutar los tests con soporte para análisis de cobertura implica usar el botón dedicado a esa tarea. Ese butón está localizado junto a los de "Run" y los de "Debug":



Tras usar ese botón, aparecerá un panel resumen a la derecha del editor principal con los porcentajes de cobertura de cada una de las carpetas del proyecto. El panel esquemático de la izquierda mostrará los niveles de cobertura de cada uno de los ficheros de código fuente. Además, el panel principal de edición marcará con color verde las líneas de código cubiertas y en rojo las que no.

Hacer click en la imagen para mostrarla a tamaño original.

Mantener la cobertura de los códigos tan cerca como sea posible del 100% es unos de los mejores indicadores de que tus tests está bien diseñados. Llevar a cabo ese análisis desde la consola o desde el IDE es una cuestión de gustos, pero ambas opciones son lo suficientemente cómodas y potentes como para usarlas a menudo.

25 diciembre 2014

Exportando e importando un virtualenv

Una cosa que he aprendido recientemente de los entornos virtualenv es que facilitan la exportación de proyectos.

Cuando le das un proyecto a un amigo o colaborador, dentro de un archivo comprimido, tienes que decirle qué dependencias instalar para ejecutar el proyecto. Afortunadamente virtualenv (bueno, en realidad pip) te da una manera automatizada de hacerlo.

Supón que tenemos una carpeta de proyecto que quieres exportar y supón que has hecho un virtualenv para ese proyecto. Con tu virtualenv activado, ejecuta:

(env)dante@Camelot:~/project-directory$ pip freeze > requirements.txt


Esto creará un  fichero llamado requirements.txt. En ese fichero, pip colocará todos los nombres de los paquetes instalados en el virtualenv, junto con sus versiones. Ese fichero generado (en nuestro caso requirements.txt) debería se incluido en el paquete que exportemos.

Para importar un proyecto exportado de esta manera, el importador debería descomprimir la carpeta de proyecto y crear un virtualenv en esa localización. Con ese virtualenv activado, pip debería ejecutarse de esta manera:

(env)otherguy@host:~/project-directory$ pip install -r requirements.txt

Esta llamada a pip instalará todos los paquetes y versiones incluidos en el fichero requierements.txt. Fácil y eficiente.