Cobertura con NetBeans y Hudson

7 02 2009

Este artículo como PDF.

Introducción

Una vez tenemos montado un entorno TDD, y si efectivamente estamos trabajando con esta orientación, nuestro proyecto empezará a acumular tests unitarios con JUnit o cualquier otro framework lo que permitirá a nuestro servidor de integración (Hudson) utilizarlos como tests de regresión.

Si estamos siendo estrictos en la aplicación de una metodología TDD, en principio el grado de cobertura del código (el porcentaje de líneas de código que son evaluadas por un test) debería estar cerca del 100% por aquello del “ escribe primero el test y luego el código que lo supera”. Sin embargo bien porque seamos principiantes aplicando la metodología o bien porque escribamos los tests como parte del proceso de calidad en una metodología diferente, puede resultar muy complicado calcular el grado de cobertura mediante técnicas manuales. Incluso en un entorno TDD maduro, la propia complejidad del software puede complicar extraer esta información. Es por ello que se han desarrollado una serie de frameworks y herramientas que permiten automatizar este proceso.

Como decía, no estamos solos ante el peligro y tenemos herramientas diversas y con todo tipo de licencias. Por citar algunas: Clover, EMMA, VectorCAST o Cobertura. Mi elección en este caso será la última por varios motivos: es un producto open source (por los que tengo debilidad, especialmente cuando uno es autónomo), ofrece tasks para usarlo desde Ant y tiene plugins para Hudson. Es una lástima que no tenga un plugin nativo para NetBeans, aunque como el IDE nos permite ejecutar targets de Ant fácilmente, se lo podemos perdonar.

Por cierto, existe un plugin de análisis de cobertura de código para NetBeans. No he jugado mucho con él, pero parece que es un servicio que sólo puede activarse para proyectos de JSE y no está disponible, por ejemplo, para un proyecto web. Si alguien tiene experiencia con el mismo, se agradecerá el feedback.

Gran parte de la información necesaria que he usado para escribir esta entrada la he obtenido de este post .

Configurar Cobertura en el NetBeans

El proceso es sencillo:

  1. descargar el software,

  2. instalarlo,

  3. modificar el build.xml del NetBeans.

Así pues, lo primero que tenemos que hacer es descargarnos el software desde SourceForge. Nosotros vamos a contentarnos con la versión binaria y dejaremos el hacking para otro momento. A continuación procederemos a la instalación que no es más que descomprimirlo en una carpeta de la máquina de desarrollo. Yo tengo la costumbre de instalar estas cosas en el directorio /srv y además suelo crear un enlace simbólico (que será el que referencie desde los proyectos) que no incluya el número de versión para que cada vez que haga una actualización no tenga que recorrer todos los scripts para corregir la referencia.

root@hargon:/srv# tar -zxf cobertura-1.9.1-bin.tar.gz
root@hargon:/srv# ln -s cobertura-1.9.1 cobertura
root@hargon:/srv# ls -alh total 684K
drwxr-xr-x  7 root      root    4,0K 2009-02-07 13:42 .
drwxr-xr-x 21 root      root    4,0K 2009-02-07 11:23 ..
lrwxrwxrwx  1 root      root      15 2009-02-07 13:42 cobertura -> cobertura-1.9.1
drwxr-xr-x  4 root      root    4,0K 2009-02-04 21:23 cobertura-1.9.1
-rw-r--r--  1 root      root    649K 2009-02-04 21:40 cobertura-1.9.1-bin.tar.gz
root@hargon:/srv# rm cobertura-1.9.1-bin.tar.gz

Como ya he explicado en alguna ocasión, NetBeans usa scripts de Ant para realizar todas sus tareas y están modularizados de manera que podamos introducir nuestros propios targets y personalizaciones. En particular el fichero que podemos “tocar” tranquilamente es el build.xml que está en la raíz del proyecto así que, mientras no diga lo contrario, todos los fragmentos de código deberán picarse en este fichero.

Lo primero que tendremos que hacer proporcionar a Ant los diferentes tasks proporcionados por Cobertura.

<path id="cobertura.classpath">
    <fileset dir="${cobertura.dir}">
        <include name="cobertura.jar" />
        <include name="lib/**/*.jar" />
    </fileset>
</path>

<taskdef classpathref="cobertura.classpath"
    resource="tasks.properties" />

Esto es uso estándar de Ant. La ruta a la raíz del frameworks lo indicamos en la propiedad cobertura.dir que a su vez cargamos del fichero de propiedades privadas. Usamos el fichero de propiedades privadas (nbproject/private/private.properties) porque la ruta del framework tiene sentido que pueda ser diferente en las máquinas de desarrollo y en el servidor de integración y, recordemos, si tenemos bien montado el repositorio de código el directorio nbproject/private no suele incluirse en el mismo. Para que todo esto funcione deberemos cargar desde el build.xml directamente dicho fichero de propiedades:

<property file="nbproject/private/private.properties"/>

y efectivamente añadir la propiedad en nbproject/private/private.properties :

cobertura.dir=/srv/cobertura

Los pasos para usar Cobertura son los siguientes:

  1. instrumentalizar el código bajo testing,

  2. ejecutar los tests unitarios con JUnit que genera los datos de cobertura y

  3. parsear los datos de cobertura para generar informes.

Instrumentalizar el código

Cobertura (y la mayoría de de frameworks de este tipo) funciona de manera que al ejecutar los tests unitarios se analiza qué parte de las clases bajo testing están siendo accedidas por las pruebas y qué partes no y se vuelca esta información en un archivo. Para que el framework de testing (en nuestro caso JUnit) pueda obtener esta información, Cobertura habrá tenido que instrumentalizar previamente las clases bajo test. Es decir, antes de ejecutar los tests, habremos tenido que permitir a Cobertura que genere un bytecode modificado (instrumentalizado) para cada una de las clases bajo testing. Es un proceso similar a como funcionan algunos profilers . Para obtener las clases instrumentalizadas lo haremos de la siguiente manera:

<target name="cobertura-instrument" depends="compile">
    <cobertura-instrument
        todir="${cobertura.classes.dir}"
        datafile="${cobertura.ser.file}" >

    <fileset dir="${build.classes.dir}">
        <include name="**/*.class"/>
    </fileset>
    </cobertura-instrument>
</target>

Creo que el target se entiende bastante fácilmente: básicamente estamos especificando donde guardaremos las las clases instrumentalizadas ( cobertura.classes.dir ), en qué fichero se escribirán los datos de cobertura ( cobertura.ser.file ) y dónde residen las clases a instrumentalizar ( build.classes.dir ). Esta última propiedad es una de las propiedades estándar que residen en el nbproject/properties ; las otras dos deberemos introducirlas en dicho fichero.

cobertura.ser.file=${build.dir}/cobertura.ser
cobertura.classes.dir=${build.dir}/cobertura/classes

Como nuestro target requiere los bytecode de las clases a instrumentalizar tiene como dependencia el target estándar compile que es el que usa NetBeans para compilar el proyecto. Entre las dependencias del propio compile se encuentra el target init que, entre otras cosas, es el encargado de cargar el fichero de propiedades (y por eso nosotros no tenemos que hacer de manera explícita la carga del mismo).

Ejecutar los tests

El siguiente paso consiste en ejecutar los tests sobre las clases instrumentalizadas para así extraer la información de cobertura. Aquí también podemos aprovecharnos de los scripts generados por NetBeans. Nuestro nuevo target , que llamaremos test-cobertura básicamente será una copia del target test (que es el que normalmente usa NetBeans para ejecutar las diferentes tests unitarios) con pequeñas modificaciones. Esta es la pinta que debería tener:

<target name="cobertura-test"
    depends="set-cobertura-file, init,compile-test,
    -pre-test-run,cobertura-instrument,
    -do-test-run,test-report,-post-test-run,-test-browse">
</target>

Con respecto al original, hemos añadido dos nuevas dependencias. Por un lado cobertura-instrument que es una referencia al target anterior para asegurarnos que existen las clases instrumentalizadas. Por otro lado hemos definido otra dependencia a otro target que también debemos construir y que muestro a continuación:

<target name="set-cobertura-file" depends="init">
    <property
        name="test-sys-prop.net.sourceforge.cobertura.datafile"
        value="${cobertura.ser.file}"/>
</target>

Este target es necesario para establecer un parámetro de sistema que debemos pasar a todos los tasks de JUnit usados en los diferentes targets de los ficheros de scripting incluidos desde el build.xml (en particular el build-impl.xml ). Necesitamos pasar este parámetro para que los tasks del JUnit sepan dónde escribir la información de cobertura mientras ejecuta los tests.

El nombre de la propiedad de sistema a pasar es net.sourceforge.cobertura.datafile el porqué se pasa el parámetro de sistema en una propiedad con un nombre algo diferente, lo tengo explicado en este otro post . El valor de la propiedad, como ya hemos visto (pues es un propiedad que ya he necesitado en targets anteriores), está dentro del fichero nbproject/project.properties y por eso nuestro target tiene una dependencia del target init que es el que se encargaba de cargar las propiedades definidas en ese fichero.

Falta un detalle adicional: especificar al JUnit que ejecute las clases instrumentalizadas y no las generadas directamente en la fase de compilación. Para hacer esto sólo tenemos que introducir unos pequeños cambios en el classpath utilizado por los targets de testing lo que implica cambiar la propiedad run.test.classpath definida en el nbproject/project.properties para que quede así:

run.test.classpath=\
    ${cobertura.dir}/cobertura.jar:\
    ${cobertura.classes.dir}:\
    ${javac.test.classpath}:\
    ${build.test.classes.dir}

Si hemos hecho todo correctamente al ejecutar el target cobertura-test deberían ejecutarse los tests unitarios como siempre pero además debería haberse construido el fichero build/cobertura.ser que contiene la información de cobertura.

Crear los informes de cobertura

El fichero build/cobertura.ser , como decíamos, contiene la información de cobertura pero está en formato binario porque ha tenido que generase en tiempo de ejecución de los tests de manera eficiente. Para extraer información legible hay que parsear dicho fichero. Para ello Cobertura nos proporciona el task cobertura-report .

Este task permite generar los informes en dos formatos: en XML o en HTML . Nosotros generaremos ambos. El primero lo necesitaremos para pasárselo al plugin correspondiente en el Hudson. El segundo nos proporcionará una vista agradable y legible. Veamos el target que tenemos que definir:

<target name="cobertura-report" depends="cobertura-test">
    <cobertura-report
        datafile="${cobertura.ser.file}"
        format="xml"
        destdir="${cobertura.report.dir}"
        srcdir="${src.dir}" />

    <cobertura-report
        datafile="${cobertura.ser.file}"
        format="html"
        destdir="${cobertura.report.dir}"
        srcdir="${src.dir}" />
</target>

Creo que aquí no hay mucho que explicar. Defino dónde está el fichero que contiene la información de cobertura, dónde quiero generar los informes y el formato de los mismos. Como siempre habremos de definir la propiedad cobertura.report.dir en el nbproject/project.properties :

reports.dir=${build.dir}/reports
cobertura.report.dir=${reports.dir}/cobertura-report

Si ahora ejecutamos este target deberían generarse los informes en el directorio especificado. Si le echamos un ojo a los informes de tipo HTML vemos que básicamente ha generado unas páginas al estilo de la documentación de las API pero con la información de cobertura. Una manera muy útil e intuitiva de mostrar la información.

Configurar el Hudson

Una vez hemos configurado todo lo anterior, ya tenemos hecho el trabajo duro. La configuración del Hudson, afortunadamente, es bastante sencilla. Estos son los pasos:

  1. instalar Cobertura,

  2. instalar el plugin de Cobertura y

  3. configurar el job :

    1. configurar el workspace para indicar dónde está Cobertura,

    2. añadir el cobertura-report en la lista de targets que tiene que ejecutar el Ant y

    3. configurar el directorio donde están los informes en XML

Instalar Cobertura

Para que el Ant lanzado por Hudson al construir el proyecto sea capaz de instanciar los tasks del Cobertura, éste tendrá que ser instalado en la máquina donde reside Hudson. El proceso de instalación será exactamente análogo al que hemos hecho en el entorno de desarrollo: descargar el paquete, descomprimirlo en el directorio /srv y establecer un enlace simbólico.

Instalar el plugin

Una vez tenemos Hudson instalado, añadir plugins es trivial. Desde la interfaz web vamos a Hudson -> Manage Hudson -> Manage Plugins -> Available y entonces seleccionamos Hudson Cobertura plugin y le damos a Install . El asistente entonces instala el plugin tras los cual habrá que reiniciar Hudson.

Configurar eljob

Aquí estamos presuponiendo que ya existe un job previo correctamente configurado y al que simplemente vamos a añadir el soporte de cobertura. Si no fuera así, echadle un ojo a este otro post que ya publiqué en su momento.

Lo primero que haremos es configurar el workspace para que sea capaz de encontrar Cobertura durante la fase de construcción del proyecto. Para ello, nos vamos al directorio nbproject/private/private.properties del job correspondiente y añadimos la propiedad que indica dicha ruta:

cobertura.dir=/srv/cobertura

A continuación modificaremos los targets de Ant que usa Hudson para construir el job . Si tuviéramos el target test lo eliminaríamos y a continuación añadiremos el target cobertura-report .

Lo siguiente es configurar el plugin para que utilice el informe XML generado:

  • Publish Cobertura Coverage Report : activamos la opción para que Hudson muestre la información de cobertura.

    • Cobertura xml report pattern : build/reports/cobertura-report/coverage.xml
      Aquí ponemos la ruta al fichero donde hemos generado el informe en formato XML.

    • Coverage Metric Targets : este apartado nos permite definir las métricas a partir de las cuales la construcción del job se considera estable o no.


En principio ya está todo. Ahora podríamos lanzar una construcción para comprobarlo. Si todo va bien, Hudson mostrará los informes de cobertura bajo el enlace Coverage Report en la raíz del job .

– 8 –

Anuncios

Acciones

Information

2 responses

11 02 2009
Montando un entorno de integración continua (Hudson + Ant + SVN + NetBeans) « Bits y Bytes

[…] 2ª Parte – Añadir métricas de cobertura de código de los tests unitarios […]

5 03 2009
Métricas de calidad con NetBeans y Hudson « Bits y Bytes

[…] básico, que luego completamos para que también se convirtiera en un verdadero entorno TDD con tests y métricas de cobertura. El siguiente paso para tener un entorno totalmente […]

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s




A %d blogueros les gusta esto: