23 jan 2011

Séparer tests unitaires et tests d’intégrations

Catégorie : Non classéJohnny Beuve @ 21 h 05 min

Des tests automatiques soignés, c’est très bien et je ne peux que vous encourager à continuer.
Des tests unitaires et des tests d’intégrations séparés c’est encore mieux. Mais comment faire?

1. Profils Maven

On peut jouer avec les profils maven. L’idée est d’exclure les tests d’intégrations en temps normal et de créer un profil maven juste pour les tests d’intégrations. L’exclusion peut se faire avec les noms des packages ou un suffixe de nom de classe. Ici un exemple de config avec le plugin de surefire et JUnit :

Build normal, on exclut les tests unitaires (mvn install) :


<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.4.3</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>surefire-test</id>
                        <phase>test</phase>
                        <goals>
                            <goal>test</goal>
                        </goals>
                        <configuration>
                            <skip>false</skip>
                            <excludes>
                                <exclude>**/integrationtest/**</exclude>
                            </excludes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

Build avec les tests d’intégrations (mvn install -Pintegrationtest)


<profiles>
        <profile>
            <id>integrationtest</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <version>2.4.3</version>
                        <configuration>
                            <skip>true</skip>
                        </configuration>
                        <executions>
                            <execution>
                                <id>surefire-itest</id>
                                <phase>integration-test</phase>
                                <goals>
                                    <goal>test</goal>
                                </goals>
                                <configuration>
                                    <skip>false</skip>
                                    <includes>
                                        <include>**/integrationtest/**</include>
                                    </includes>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>

Une alternative consiste à faire une « test suite » et à la mettre dans la liste d’inclusion. Ainsi on maîtrise exactement qu’elles classes sont exécutées lors des tests.


         <configuration>
          <includes>
            <include>**/*TestSuite.java</include>
          </includes>
          <systemProperties>
            <property>
              <name>monapplication.db.target</name>
              <value>${monapplication.db.target}</value>
            </property>
          </systemProperties>
        </configuration>

2. TestNG

Avec TestNG il suffit d’annoter la class @Test(groups = « integration ») et de mettre les groupes dans la config Surefire ou FailSafe de maven. L’avantage c’est qu’il est relativement aisé de classer ses Tests. Ici un exemple avec TestNg et le plugin surefire :

Code java :


package org.jo.testit;

import org.junit.Assert;
import org.testng.annotations.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by IntelliJ IDEA.
 */
@Test
public class CalculatorNgTest {
    static Logger logger = LoggerFactory.getLogger(CalculatorNgTest.class);

    //@Test(groups = "unittest,integrationtest", suiteName = "suite1" )
    @Test(groups = "unittest,integrationtest")
    public void testAdd() {
        logger.debug("RUNNING Test NG UNIT test!");
        Assert.assertEquals("30", 30, Calculator.add(10, 20));
        // TODO Pourquoi ce test n'est pas lancé quand on fait mvn install -Pintegrationtest
    }
}

Au niveau du plugin surefire :


                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <version>2.4.3</version>
                        <configuration>
                            <groups>integrationtest</groups>
                        </configuration>
                    </plugin>      

3. Plugin FailSafe

Le plugin FailSafe permet de simplifier la config. Surtout il fait le post-integrationtest même si les tests d’intégrations plantent, ce que ne fait pas Surefire. L’idée est donc d’utiliser Surefire pour les tests unitaires et FailSafe pour les tests d’intégrations en attachant l’exécution à la phase integration-test par exemple. Attention, par convention le plugin FailSafe exécute les tests src/test/java/<above packages>/*IT.java. Si par hasard vos tests ne respectaient pas cette règle de nommage il faut les inclure explicitement. Ici un exemple de config :


          <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.7.1</version>
                <configuration>
                    <groups>integrationtest</groups>
                    <includes>
                        <include>**/*Test.java</include>
                    </includes>
                </configuration>
                <executions>
                    <execution>
                        <id>integration-test</id>
                        <phase>integration-test</phase>
                        <goals>
                            <goal>integration-test</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>verify</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>     

4. @IfProfileValue de spring

Si vous faites du Spring l’annotation  @IfProfileValue(name= »nom-de-la-variable », value= »valeur ») permet de lancer le test que si la variable d’environnement est valide. Pour qu’ils soient exécutés, il suffit de les lancer avec -Dnom-de-la-variable=valeur. Si on utilise maven il faudra spécifier une variable système. Exemple ici :

Code java :


package org.jo.testit;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 */
@ContextConfiguration(locations = {"/META-INF/spring/config.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class CalculatorTest {
    static Logger logger = LoggerFactory.getLogger(CalculatorTest.class);

    @Test
    @IfProfileValue(name = "group-unittest", value = "true")
    public void testAdd() {
        logger.debug("RUNNING UNIT test!");

        Assert.assertEquals("30", 30, Calculator.add(10, 20));
    }
}      

Config maven :


                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <version>2.4.3</version>
                        <configuration>
                            <skip>true</skip>
                        </configuration>
                        <executions>
                            <execution>
                                <id>surefire-itest</id>
                                <phase>integration-test</phase>
                                <goals>
                                    <goal>test</goal>
                                </goals>
                                <configuration>
                                    <skip>false</skip>
                                    <systemProperties>
                                        <property>
                                            <name>group-unittest</name>
                                            <value>true</value>
                                        </property>
                                        <property>
                                            <name>group-integrationtest</name>
                                            <value>true</value>
                                        </property>
                                    </systemProperties>
                                </configuration>      

Il existe sûrement d’autres manières de procéder. A vous de choisir selon les besoins et le contexte du projet. Vous trouvez pour chacun des points abordés ici un exemple dans un projet maven dans ce Zip.

Nous n’avons pas parler des tests fonctionnels, peut-être dans un autre article ;-)

Mots-clefs : , ,


10 mar 2010

Mise en place de JMX avec Spring

Catégorie : Non classéJohnny Beuve @ 22 h 25 min

JMX (Java Management eXtension) permet de superviser et d’administrer les ressources logiciels s’exécutant sur une JVM locale ou distante. Cette approche permet donc d’inspecter l’état d’une application en production, de modifier son comportement, de notifier des évènements sans avoir une interruption de service.

La plupart des serveurs d’applications java incluent JMX. (JBoss, Websphère, Weblogic).

Cet article décrit rapidement JMX et propose un exemple très simple avec et sans le framework Spring.

JMX est composé de trois couches :

  • La couche instrumentation définit les ressources qui vont être monitorées. Ces ressources sont des MBeans qui ressemblent à de simples POJOs.
  • La couche agent qui permet le déploiement des MBeans, les notifications et le monitoring. Toutes communications avec les ressources monitorées passent donc par cette couche. Le composant principal de cette couche est le MBeanServeur qui enregistre notamment les MBeans.
  • La couche Remote Management gère les communications externes avec les agents au travers des adapteurs et des connecteurs. Les adapteurs permettent à un client de communiquer avec un MBeanServeur selon un protocole donné.

Quelles sont les étapes pour enregistrer un MBean

  • Il faut créer une interface dont le nom finit par MBean. Par exemple InstrumentMBean
  • Implémenter cette interface dans une classe dont le nom est celui de l’interface mais sans MBean à la fin. Par exemple Instrument. Attention la classe implémentant l’interface du MBean doit se trouver dans le même package, et avoir un constructeur sans paramètre.
  • Ensuite il suffit d’enregistrer cet MBean dans un MBeanServeur.

    • Instancier le MBean
    • Obtenir une référence de MBeanServer
    • Construire un ObjectName (Un ObjectName est composé de deux parties, le domaine et une paire (nom,valeur) séparée par des : , cela donne :  mon.domaine:type=MonMBeanAMoi)
    • Enregistrer le Mbean dans le MBeanServer en utilisant l’ObjectName unique que vous avez créé.

Votre MBean est enregistré, vous pouvez maintenant le consulter en utilisant la jconsole.

Faisons un exemple :

Interface :


package tuto.jmx;

public interface InstrumentMBean {
	public boolean isRunning();
	public void setRunning(boolean running);
	public void start();
	public void stop();
}


Implémentation :


package tuto.jmx;

public class Instrument implements InstrumentMBean {
	boolean running = false;

	@Override
	public boolean isRunning() {
		return running;
	}

	@Override
	public void setRunning(boolean running) {
		this.running = running;
	}

	@Override
	public void start() {
		running = true;
	}

	@Override
	public void stop() {
		running = false;
	}
}


Agent :


package tuto.jmx;

import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;

public class AgentMain {
	public static void main(String[] args) {
		Instrument instrument = new Instrument();
		MBeanServer ms = ManagementFactory.getPlatformMBeanServer();

		try {
			ObjectName oa = new ObjectName("mon.domaine:type=MonInstrument");
			ms.registerMBean(instrument, oa);
			System.out.println("Agent Running...");
			Thread.sleep(60000l);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Lancer le main et lancer simplement la jconsole (attention si vous utilisez java5, il faut lancer le main avec les paramètres suivants pour pouvoir se connecter en remote avec la jconsole « -Dcom.sun.management.jmxremote »)

On se connecte :



Connexion JConsole

on regarde les mbeans :



InstrumentMBean

Comment faire avec Spring :

  • Il faut juste créer un MBean. Par exemple ObservationBean
  • Il faut déclarer cet MBean dans le document de configuration de Spring

Voilà c’est suffisant vous pouvez maintenant lancer votre application Spring et consulter via la JConsole le MBean

Faisons un exemple :

Création du MBean :


package org.jo.dto;

import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;

/**
 * MBean dispayed in JMX console.
 */
@ManagedResource
public class ObservationBean {
	private Integer nbrEvent = 0;
	private Integer nbrBoundaryEvent = 10;

	public ObservationBean() {
		super();
	}

	public ObservationBean(Integer nbrEvent, Integer nbrBoundaryEvent) {
		super();
		setNbrBoundaryEvent(nbrBoundaryEvent);
		setNbrEvent(nbrEvent);
	}

	@ManagedAttribute
	public Integer getNbrEvent() {
		return nbrEvent;
	}

	public void setNbrEvent(Integer nbrEvent) {
		this.nbrEvent = nbrEvent;
	}

	@ManagedAttribute
	public Integer getNbrBoundaryEvent() {
		return nbrBoundaryEvent;
	}

	@ManagedAttribute
	public void setNbrBoundaryEvent(Integer nbrBoundaryEvent) {
		this.nbrBoundaryEvent = nbrBoundaryEvent;
	}

	@ManagedAttribute
	public boolean isOk() {
		return nbrEvent < nbrBoundaryEvent;
	}

	@Override
	public String toString() {
		return "System is " + (isOk()? "OK ": "HS ") + "(" + getNbrEvent()
				+ " event(s) for a limit of " + getNbrBoundaryEvent() + ")";
	}
}

@ManagedResource : cette annotation déclare le Bean comme MBean.
@ManagedAttribute : spécifie les attributs accessibles par les clients JMX.

Le document de configuration Spring est très simple :


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<context:annotation-config />
	<context:mbean-export />

	<bean class="org.jo.jmx.ObservationEntry">
		<constructor-arg ref="observation" />
	</bean>

	<!-- Observation under jmx -->
	<bean id="observation" class="org.jo.dto.ObservationBean">
	</bean>

</beans>

Pour activer les annotations :

<context:annotation-config />


Pour exporter les MBeans Standards et des @ManagedResource

<context:mbean-export />

Conclusion

JMX est facile à mettre en place (encore plus facile avec Spring). Le champ d’application est large. On peut imaginer par exemple un MBean qui permet de faire fonctionner votre application en mode dégradé. Un autre MBean qui donne la possibilité de recharger les propriétés de votre application. Un MBean qui vous donne une vue générale de l’état des ressources dont a besoin votre application pour fonctionner normalement. Les administrateurs des serveurs d’applications seront enchantés.

Download

Vous trouverez ici un projet qui reprend les exemples de cet article. C’est un projet Maven2.

Mots-clefs : , ,