Problemas desplegando unidades de persistencia en JBoss 5

24 02 2009

En los últimos cursos que he dado sobre tecnologías JEE he utilizado los productos open source de Sun porque se integran bastante bien y además los considero más adecuados para formación que otras posibles alternativas. En particular, he estado utilizando NetBeans y Glassfish para explicar EJB 3.0 y las apis de persistencia y transaccionalidad (JPA y JTA).

Hace unos días me bajé JBoss AS 5 para ver las novedades que presentaba (ya que en el pasado trabajé bastante con él) y al ir a desplegar algún jar que había funcionado perfectamente en Glassfish me ha dado problemas. Como sospechaba, el problema estaba en el context.xml y en que JBoss AS se ha vuelto un poco más pijo.

Veámoslo con un ejemplo. Este es el persistence.xml que funcionaba sin problemas en Glassfish y que reventaba en el JBoss:

<?xml version="1.0" encoding="UTF-8"?>
<persistence>
  <persistence-unit name="Banco" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>banco</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="hibernate.hbm2ddl.auto" value="update"/>
    </properties>
  </persistence-unit>
</persistence>

Esta es la salida que producía en el log del JBoss:

2009-02-24 19:28:42,832 ERROR [org.jboss.kernel.plugins.dependency.AbstractKernelController] (HDScanner) Error installing to Parse: name=vfszip:/home/ivan/jboss-5.0.0.GA/server/all/deploy/titan.jar state=Not Installed mode=Manual requiredState=Parse
org.jboss.deployers.spi.DeploymentException: Error creating managed object for vfszip:/home/ivan/jboss-5.0.0.GA/server/all/deploy/titan.jar
	at org.jboss.deployers.spi.DeploymentException.rethrowAsDeploymentException(DeploymentException.java:49)
	at org.jboss.deployers.spi.deployer.helpers.AbstractParsingDeployerWithOutput.createMetaData(AbstractParsingDeployerWithOutput.java:337)
	at org.jboss.deployers.spi.deployer.helpers.AbstractParsingDeployerWithOutput.createMetaData(AbstractParsingDeployerWithOutput.java:297)
	at org.jboss.deployers.spi.deployer.helpers.AbstractParsingDeployerWithOutput.createMetaData(AbstractParsingDeployerWithOutput.java:269)
	at org.jboss.deployers.spi.deployer.helpers.AbstractParsingDeployerWithOutput.deploy(AbstractParsingDeployerWithOutput.java:230)
	at org.jboss.deployers.plugins.deployers.DeployerWrapper.deploy(DeployerWrapper.java:171)
	at org.jboss.deployers.plugins.deployers.DeployersImpl.doDeploy(DeployersImpl.java:1439)
	at org.jboss.deployers.plugins.deployers.DeployersImpl.doInstallParentFirst(DeployersImpl.java:1157)
	at org.jboss.deployers.plugins.deployers.DeployersImpl.install(DeployersImpl.java:1098)
	at org.jboss.dependency.plugins.AbstractControllerContext.install(AbstractControllerContext.java:348)
	at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:1598)
	at org.jboss.dependency.plugins.AbstractController.incrementState(AbstractController.java:934)
	at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:1062)
	at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:984)
	at org.jboss.dependency.plugins.AbstractController.change(AbstractController.java:822)
	at org.jboss.dependency.plugins.AbstractController.change(AbstractController.java:553)
	at org.jboss.deployers.plugins.deployers.DeployersImpl.process(DeployersImpl.java:781)
	at org.jboss.deployers.plugins.main.MainDeployerImpl.process(MainDeployerImpl.java:545)
	at org.jboss.system.server.profileservice.hotdeploy.HDScanner.scan(HDScanner.java:290)
	at org.jboss.system.server.profileservice.hotdeploy.HDScanner.run(HDScanner.java:221)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
	at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:317)
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:150)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$101(ScheduledThreadPoolExecutor.java:98)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.runPeriodic(ScheduledThreadPoolExecutor.java:181)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:205)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
	at java.lang.Thread.run(Thread.java:619)
Caused by: org.jboss.xb.binding.JBossXBException: Failed to parse source: Failed to resolve schema nsURI= location=persistence
	at org.jboss.xb.binding.parser.sax.SaxJBossXBParser.parse(SaxJBossXBParser.java:203)
	at org.jboss.xb.binding.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:168)
	at org.jboss.deployers.vfs.spi.deployer.JBossXBDeployerHelper.parse(JBossXBDeployerHelper.java:199)
	at org.jboss.deployers.vfs.spi.deployer.JBossXBDeployerHelper.parse(JBossXBDeployerHelper.java:170)
	at org.jboss.deployers.vfs.spi.deployer.SchemaResolverDeployer.parse(SchemaResolverDeployer.java:132)
	at org.jboss.deployers.vfs.spi.deployer.SchemaResolverDeployer.parse(SchemaResolverDeployer.java:118)
	at org.jboss.deployers.vfs.spi.deployer.AbstractVFSParsingDeployer.parseAndInit(AbstractVFSParsingDeployer.java:256)
	at org.jboss.deployers.vfs.spi.deployer.AbstractVFSParsingDeployer.parse(AbstractVFSParsingDeployer.java:188)
	at org.jboss.deployers.spi.deployer.helpers.AbstractParsingDeployerWithOutput.createMetaData(AbstractParsingDeployerWithOutput.java:323)
	... 27 more
Caused by: org.jboss.xb.binding.JBossXBRuntimeException: Failed to resolve schema nsURI= location=persistence
	at org.jboss.xb.binding.sunday.unmarshalling.SundayContentHandler.startElement(SundayContentHandler.java:313)
	at org.jboss.xb.binding.parser.sax.SaxJBossXBParser$DelegatingContentHandler.startElement(SaxJBossXBParser.java:401)
	at org.apache.xerces.parsers.AbstractSAXParser.startElement(Unknown Source)
	at org.apache.xerces.impl.xs.XMLSchemaValidator.startElement(Unknown Source)
	at org.apache.xerces.xinclude.XIncludeHandler.startElement(Unknown Source)
	at org.apache.xerces.impl.XMLNSDocumentScannerImpl.scanStartElement(Unknown Source)
	at org.apache.xerces.impl.XMLNSDocumentScannerImpl$NSContentDispatcher.scanRootElementHook(Unknown Source)
	at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source)
	at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
	at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
	at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
	at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
	at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source)
	at org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source)
	at org.jboss.xb.binding.parser.sax.SaxJBossXBParser.parse(SaxJBossXBParser.java:199)
	... 35 more
2009-02-24 19:28:42,848 WARN  [org.jboss.system.server.profileservice.hotdeploy.HDScanner] (HDScanner) Failed to process changes
org.jboss.deployers.client.spi.IncompleteDeploymentException: Summary of incomplete deployments (SEE PREVIOUS ERRORS FOR DETAILS):

*** CONTEXTS IN ERROR: Name -> Error

vfszip:/home/ivan/jboss-5.0.0.GA/server/all/deploy/titan.jar -> org.jboss.xb.binding.JBossXBRuntimeException: Failed to resolve schema nsURI= location=persistence

	at org.jboss.deployers.plugins.deployers.DeployersImpl.checkComplete(DeployersImpl.java:863)
	at org.jboss.deployers.plugins.main.MainDeployerImpl.checkComplete(MainDeployerImpl.java:665)
	at org.jboss.system.server.profileservice.hotdeploy.HDScanner.scan(HDScanner.java:293)
	at org.jboss.system.server.profileservice.hotdeploy.HDScanner.run(HDScanner.java:221)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
	at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:317)
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:150)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$101(ScheduledThreadPoolExecutor.java:98)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.runPeriodic(ScheduledThreadPoolExecutor.java:181)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:205)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
	at java.lang.Thread.run(Thread.java:619)

La solución ha sido más o menos sencilla: definir el espacio de nombres XML tal y como sigue y la unidad de persistencia se ha desplegado sin más problemas:

<?xml version="1.0" encoding="UTF-8"?>
<persistence
    version="1.0"
    xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
   <persistence-unit name="titan">
      <jta-data-source>java:/DefaultDS</jta-data-source>
      <properties>
         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      </properties>
   </persistence-unit>
</persistence>

Tendré que echar un ojo a la especificación de JPA para ver si definir el espacio de nombres es obligatorio, aunque me parece recordar que esto no era una exigencia en versiones anteriores de JBoss AS…





Configuración de logging global en Java

19 02 2009
Duke, the Java Mascot, in the waving pose. Duk...
Image via Wikipedia

Hoy me he pasado unas cuantas horas peleándome con una tontería. La típica cosa que cuando ves lo que es te da una rabia infinita 😛

Aunque en el pasado había trabajado bastante con JBoss AS aún no me había dado tiempo a probar la versión 5 que se liberó en diciembre, así que he decidido utilizarlo para un pequeño proyectito y así tener la excusa de ver qué novedades presenta.

Mi sorpresa ha sido que al ponerlo en marcha se ha puesto a sacar tal cantidad de información de logging por la consola que el pobre ha tardado más de 10 minutos en arrancar. Cara de póquer. Como me conozco el servidor, me voy a ver la configuración de logging del mismo que está en:

JBOSS_HOME/server/default/conf/jboss-log4j.xml

Si no recuerdo mal en anteriores versiones el fichero se llamaba a secas log4j.xml, pero al echarle un vistazo su estructura es la misma que en versiones anteriores. Sigue utilizando log4j en vez de la api de logging de de la JSE y por defecto configura dos appenders: uno de consola con límite en el nivel WARN y otro de fichero.

Ahora sí que no entendía nada: ¿cómo es posible que la consola esté sacando tanto mensaje de logging y además con un nivel TRACE cuando la configuración limita al nivel a WARN?

Sigo investigando y compruebo que la información de logging que se corresponde con lo que yo voy configurando en dichero fichero sale por la salida estándar y que los mensajes “okupas” en realidad están saliendo por el canal de error… Además otros dos detalles, estos mensajes se corresponden con clases de la JSE y no tienen el mismo formato (pattern) que el resto de mensajes… Es más, parece que tienen formato del paquete de logging estándar de JSE…

Fallo localizado, tengo activado el logging estándar de la JSE (javax.util.logging) al nivel más alto. Ahora sólo falta averiguar por qué. Tras probar una serie de cosas estrambóticas, el fallo estribaba en que en algún momento (que no logro recordar) había cambiado la configuración global del logging estándar; ¿cómo se hace esto? Editando un fichero que se encuentra en la carpeta lib de la JRE instalada, en particular:

JAVA_HOME/jre/lib/logging.properties

En dicho fichero tenía establecido lo que llaman el “Default global logging level” a ALL. Al cambiarlo a WARNING todo volvió a la normalidad.

../..
# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers.  For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= WARNING
../..
Reblog this post [with Zemanta]




DZone y Refcardz

11 02 2009

Una de las suscripciones que tengo en mi GoogleReader es el feed de DZone. En esta web se pueden leer artículos muy interesantes, bien redactados y estructurados y, en muchas ocasiones, escritos por eminencias en diferentes campos de la ingeniería del software. Las temáticas que cubren son diversas (patrones de diseño, antipatrones), metodologías, lenguajes, tecnologías concretas, tendencias, comparativas, etc. Cuando aparece un nuevo post suele ser uno de los feeds que tienen preferencia en mi orden de lectura.

Al margen de los artículos, son los encargados de publicar las Refcardz. Son unos documentos PDF con un estilo vistoso, funcional, uniforme y, sobre todo, breve que cuben en pocas páginas las ideas fundamentales de una tecnología concreta. Generalmente encajan en alguna de las siguientes categorías: herramientas, lenguajes y frameworks y, más raramente, arquitectura.

Yo las utilizo bajo dos puntos de vista:

  1. Cuando conozco la tecnología, las utilizo como guía de referencia rápida (por ejemplo, la de NetBeans tiene tablas con todas las combinaciones de teclas o con las diferentes plantillas de código y la de JPA tiene resúmenes de todas las etiquetas disponibles). Además suelen incluir ejemplos muy prácticos que permiten recordar de un vistazo cómo usar la tecnología que están ilustrando.
  2. Cuando es una tecnología que no conozco en profundidad o que me es desconocida las utilizo para hacerme una primera idea de las posibilidades que ofrece. Además suelen ofrecer punteros a otras fuentes de documentación o libros.

Estas fichas tecnológicas tienen una gran calidad de edición y de contenidos (se nota que el proceso de producción hasta que se hacen disponibles está muy cuidado) y están escritas por expertos en la materia que ilustran.

Además son GRATIS y se pueden usar sin restricciones. El truco es que el autor de la misma incrementa su reputación por tener la ficha y además se reserva un pequeño espacio donde éste puede publicitar su libro (siempre referente a la tecnología de la ficha en custión).

También existe un feed que informa de la publicación de nuevas fichas. Cuando aparece una nueva entrada no puedo evitar bajarme rápidamente el PDF.





Presentación de administración de Tomcat

9 02 2009
Apache Tomcat
Image via Wikipedia

Hola,

Os cuelgo una presentación (creo que completita) que preparé para un curso de administración de Tomcat. Esta vez os lo cuelgo en un formato decente: en PDF.

La distribuyo bajo licencia licencia. Si alguien quiere hacer un uso comercial que se ponga en contacto conmigo.

En algunas transparencias aparece en rojo “Ejercicio MxEy”, lo que quiere decir que en ese punto de la presentación había previsto un ejercicio correspondiente al módulo x con número de ejercicio y. Si alguien quiere los enunciados y las soluciones, que me los pida sin ningún problema (es una pena que wordpress no deje subir zips o tars).

Aprovecho para comentar que en este momento soy profesional freelance y que me ofrezco para temas de asesoría y/o formación en los temas sobre los que posteo.

El índice de la presentación es el siguiente:

  1. Introducción
  2. Instalación de Tomcat
  3. Aplicaciones web con Java
  4. Configurar Tomcat
  5. Gestionar aplicaciones web
  6. Características avanzadas
  7. Conexión con bases de datos
  8. Conectores
  9. Seguridad
  10. Alojamiento compartido
Reblog this post [with Zemanta]




Presentación desarrollo del software para no informáticos

8 02 2009

Os cuelgo una presentación sobre ingeniería informática y desarrollo de software que hice hace unos meses para una audiencia no informática que se dedica a programar. Hablo de bastantes temas pero desde un punto de vista muy divulgativo y poco profundo. El formato es un pptx (lo siento :-P).

La distribuyo bajo licencia licencia y se puede descargar aquí. Si alguien quiere utilizarla con propósitos comerciales que se ponga en contacto conmigo.

El índice de la presentación es el siguiente:

  1. Ingeniería del Software y Metodologías
    1. La problemática de la construcción de software
    2. La ingeniería del software como solución
    3. Partes fundamentales de la ingeniería del software
    4. Ciclos de vida de construcción de software
  2. Arquitectura de aplicaciones
    1. Componentes de un sistema
    2. Evolución y tipos de arquitecturas
    3. Patrones de diseño
  3. Metodologías
    1. Por qué las metodologías tradicionales no son suficiente
    2. Metodologías ágiles
    3. TDD
  4. Entornos y herramientas
    1. Entornos de desarrollo
    2. IDEs
    3. Plugins e integración de herramientas
  5. Buenas prácticas
    1. Buenas prácticas generales
    2. Particularidades del software científico
    3. Checkpoints
  6. Programación paralela
    1. Qué es la programación paralela
    2. Conceptos y clasificaciones
    3. Programación paralela
    4. Qué ayudas tiene el programador
  7. Asincronía y colas
    1. Sincronía vs asincronía
    2. Sistemas de colas
  8. Midiendo
    1. Debugging
    2. Logging
    3. Profiling
    4. Benchmarking
  9. Definición de flujos




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 –





Pasar propiedades de sistema a los tasks JUnit de los scripts de NetBeans

6 02 2009

Como ya he explicado alguna vez, NetBeans utiliza scripts de Ant para realizar las diferentes tareas que lleva acabo el IDE, como compilar el proyecto, lanzar los tests, ejecutarlo, desplegarlo, etc.

NetBeans genera los scripts de manera modular de manera que el usuario pueda personalizarlo de forma adecuada. Genera el build.xml principal en la raíz del proyecto que a su vez incluye otros tantos recursos del subdirectorio de nbproject. Estos recursos son ficheros de propiedades y de scripting adicionales con la implementación real de los diferentes targets.

La idea de esta estructuración es que los ficheros con targets en el directorio nbproject (build-impl.xml, por ejemplo) nunca deberían tocarse ya que se pueden regenerar (y por tanto se perdería toda nuestra personalizacion) en función de las diferentes opciones que definamos para nuestro proyecto a través de la propia interfaz del IDE. Por contra, el build.xml principal no se regenerará y es por tanto en este fichero donde personalizaremos los scripts (añadir nuevos targets, definir propiedades, etc.).

El problema surge cuando queremos modificar el comportamiento de algunos de los targets definidos en los ficheros “intocables”. Por ejemplo, un requerimiento que puede surgir fácilmente (veremos algún ejemplo en próximos posts) es la necesidad de pasar parámetros de sistema a los tasks que ejecutan las pruebas unitarias con JUnit.

Afortunadamente el equipo de NetBeans ha previsto esta situación y la solución es bastante sencilla. Imaginemos que queremos pasar una propiedad de sistema que se llama MiPropiedad con valor MiValor, la solución pasa por definir en el build.xml una propiedad que se llame test-sys-prop.MiPropiedad y todos los taks Junit de los scripts generados automáticamente recibirán la propiedad MiPropiedad con el valor que le hayamos indicado. Si necesitáramos pasar más de una propiedad repetiríamos el mismo proceso con otro nombre de parámetro.

En código, habría que incluir el siguiente tag en el build.xml principal:

<property name="test-sys-prop.MiPropiedad" value="MiValor" />

Si queréis entender el motivo exacto y hackear los scripts de NetBeans, echadle un ojo a este post.