L’intégration continue avec Cargo

Dans un projet J2EE, il est toujours utile de pouvoir déployer son application sur un serveur et plus encore pour faire de l’intégration en continu avec des tests fonctionnels. La plupart du temps, on utilise un serveur dédié pour les tests et les outils livrés avec pour gérer les déploiements.

Cargo utilise les outils de chaque serveur et livre une interface unifiée pour administrer les serveurs J2EE. En bref, Cargo permet d’installer, de configurer, de lancer et d’arrêter des serveurs dans une approche multi-conteneurs. De plus, on peut enfin l’utiliser à travers plusieurs outils, puisqu’il fournit des extensions pour Netbeans, Ant, IntelliJ, Maven 1 et 2, et bien sûr une API java.

Dans cet article, nous présenterons d’abord Cargo et son fonctionnement, puis un exemple concret d’intégration continue dans un projet Maven avec Cargo et Selenium.

Oui mais pourquoi ?

Cargo peut être utilisé pour concevoir des outils destinés à simplifier la vie des développeurs, en permettant de mettre en place un serveur J2EE et de déployer ses applications dessus. On peut aussi l’utiliser pour mettre en place des environnements de tests d’intégration. À l’aide du plugin maven, nous allons automatiser l’installation, la configuration et le lancement d’un serveur tomcat pour nos tests. L’avantage principal de cette méthode est de ne plus dépendre du conteneur choisi (dans la limite des versions supportées par Cargo).

Notez qu’il est également possible d’utiliser Cargo pour packager une configuration fonctionnelle complète de votre serveur J2EE, intégrant la ou les archives war/ear à livrer.

Tour des fonctionnalités

Les fonctions de Cargo sont réparties en différents modules regroupant des tâches communes allant de la configuration au déploiement.

Container

Dans l’API, un Container est l’interface de plus haut niveau englobant un conteneur J2EE. Deux types de conteneur sont disponibles :

  • local
  • remote

Le conteneur local représente un serveur accessible par le système de fichier local de la machine exécutant Cargo. Il fournit beaucoup de possibilités :

  • Configurer le classpath
  • Lancer ou arrêter
  • Lancer dans la même JVM (uniquement avec Jetty)
  • Installer

Le conteneur remote de son côté représente un serveur déjà accessible par le réseau. De ce fait, Cargo ne peut en prendre le contrôle et se contente de permettre le déploiement à distance via un Deployer.

Configuration

Le module de configuration comme son nom l’indique permet de configurer un Container. Là encore, ils sont divisés en deux types : les local sont accessibles par le système de fichiers et les runtime le sont par le réseau.

En local, Cargo prend en charge les configurations existantes d’un serveur déjà configuré par vos soins ou simplement une configuration standalone qu’il construira entièrement et surtout indépendamment de vos chères installations.

La configuration runtime permettra de modifier les propriétés administrables du conteneur par le réseau, mais c’est une coquille vide.

Deployment

Le module de déploiement, vous l’aurez deviné, englobe l’ensemble des fonctionnalités de déploiement. Il se divise en deux sous modules :

  • Deployable
  • Deployer

Les Deployable sont les archives WAR ou EAR que l’on peut utiliser pour faire un déploiement statique. Il s’agit simplement d’installer l’archive dans le dépôt de votre conteneur de manière à ce qu’elle démarre avec le serveur. Cargo propose des Deployable génériques de type WAR et EAR, mais aussi des extensions spécialisées pour un type de conteneur. Il supporte donc les descripteurs de déploiement spécifiques à chaque serveur.

On peut aussi passer par un Deployer pour assurer le déploiement à chaud d’un Deployable. Le Deployer peut-être local ou remote selon la méthode d’accès au conteneur utilisé. Attention avec un Deployer, vous pourrez installer et/ou supprimer une archive du serveur cible.

Les conteneurs supportés

Dans la version stable, qui date de mars 2006, le nombre de conteneurs était déjà important. Mais, on est tout de même forcé de constater que peu de conteneurs à jour sont supportés. Fort heureusement, dans la version alpha du moment, plusieurs serveurs ont fait leur entrée comme Weblogic 9, et JBoss5.

Dans le cas où votre serveur J2EE n’est pas listé, il implémente peut-être comme Cargo la JSR88 : Java EE Application Deployment. Si votre serveur est supporté, ne vous réjouissez pas trop vite et vérifiez les fonctions disponibles qui peuvent être restreintes.

Cargo dans un projet Maven

Comme on l’a déjà dit, Cargo fournit un plugin pour Maven 2. Nous allons l’utiliser dans un projet J2EE pour automatiser les tests d’intégration. Dans notre cas, il s’agit de déployer une application web sur un serveur Tomcat frais que l’on utilisera pour exécuter nos tests avec Selenium. Deux configurations de projets sont possibles :

  • Module simple avec un packaging de type war.
  • Multi-module, avec un pom parent, un module fournissant le war et un dernier contenant les tests d’intégrations.

Bien que les deux solutions soient supportées, la version multi-modules est préférable. Elle permet de parfaitement séparer les tests d’intégrations des tests unitaires. En prime ce type d’architecture maven est tout simplement une bonne pratique permettant de séparer les différentes couches de l’application.

Pour un démarrage rapide et sans Selenium, Cargo propose deux archétypes permettant de générer un projet simple ou multi-modules, avec une configuration fonctionnelle basique pour déployer le war sous Tomcat5x ou Jetty.

Le projet

En partant de chacun des deux archétypes Cargo, nous avons configuré TestNG pour dérouler nos tests unitaires et nos tests d’intégration bénéficiant ainsi de la possibilité de monter des scénarii de tests fonctionnels. Enfin, nous ajouterons Selenium pour parfaire le tout.

Dans la suite de l’article, nous baserons nos exemples sur la solution conseillée de type multi-modules.

Le projet est donc composé comme suit :

  • pom parent myapp-multi de type pom.
    • module myapp-webapp de type war : contient les sources et tests unitaires de l’application.
    • module myapp-functionnal-tests de type pom : contient uniquement les tests d’intégrations.

Déployer son application

Nous voulons déployer l’application sur un serveur de qualification quand le build est stable dans notre serveur d’intégration continue. Le serveur de qualification est un Tomcat 5.5 lancé avec un manager activé. Pour éviter d’interférer avec le processus normal de construction du module myapp-webapp, nous ajoutons le plugin Cargo dans un profil que nous appelons qualif (-P qualif).

<profile>
	<id>qualif</id>
	<build>
		<plugins>
			<plugin>
				<groupId>org.codehaus.cargo</groupId>
				<artifactId>cargo-maven2-plugin</artifactId>
				<configuration>
					<wait>false</wait>
					<container>
						<containerId>tomcat5x</containerId>
						<type>remote</type>
					</container>
					<configuration>
						<properties>
							<cargo.logging>high</cargo.logging>
							<cargo.servlet.port>8080</cargo.servlet.port>
							<cargo.remote.username>manager</cargo.remote.username>
							<cargo.remote.password>tomcat</cargo.remote.password>
							<cargo.manager.url>http://${qualif.host}:${qualif.port}/manager</cargo.manager.url>
							<cargo.hostname>${qualif.host}</cargo.hostname>
						</properties>
						<type>runtime</type>
					</configuration>
					<deployer>
						<type>remote</type>
						<deployables>
							<deployable>
								<type>war</type>
								<pingURL>http://${qualif.host}:${qualif.port}/myapp/</pingURL>
								<pingTimeout>240000</pingTimeout>
								<properties>
									<context>myapp</context>
								</properties>
							</deployable>
						</deployables>
					</deployer>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<properties>
	<qualif.host>localhost</qualif.host>
	<qualif.port>8080</qualif.port>
	</properties>
</profile>

Le plugin cargo est importé, sans préciser la version, histoire de récupérer la dernière en date. Il contient trois éléments de configurations :

  • container : décrit un conteneur remote de type Tomcat5x.
  • configuration : dans l’ordre, on configure le niveau de verbosité de Cargo, le numéro du port attaché au serveur Tomcat, les login et password utilisés pour s’authentifier sur Tomcat, l’url du manager et le nom DNS ou l’IP de la machine hébergeant le serveur.
  • deployer : décrit un deployer de type remote embarquant l’archive web livrée par notre artefact. Par défaut cargo prend le package livré par le module courant comme deployable. On a juste ajouté un ping pour tester la disponibilité de la page d’accueil.

Attention : on a utilisé des propriétés Maven pour fournir les valeurs de l’adresse du serveur et de son port.

Pour tester cette configuration, on peut lancer depuis le module myapp-webapp :

mvn clean package cargo:deployer-redeploy -P qualif

On peut ajouter cette exécution à notre serveur d’intégration continue pour toujours être sûr de ce que l’on déploie sur la qualification.

Tests d’intégrations

Pour effectuer les tests, on utilisera :

  • TestNG pour organiser les scénarii de tests fonctionnels.
  • Cargo bien sûr pour déployer l’application à tester.
  • Selenium pour contrôler le navigateur internet.

Notez tout de même que nous sommes dans un packaging de type POM et que de ce fait nous devons ajouter explicitement les phases de compilations et d’exécutions des tests.

TestNG

Dans le module myapp-functional-tests, on souhaite utiliser TestNG. Il faut d’abord l’ajouter aux dépendances :

<dependency>
	<groupId>org.testng</groupId>
	<artifactId>testng</artifactId>
	<version>5.1</version>
	<scope>test</scope>
	<optional>true</optional>
	<classifier>jdk15</classifier>
</dependency>

Notez seulement l’élément classifier qui active le support Java 5 de TestNG. Nous ajoutons maintenant le compilateur Maven avec la même précision concernant le support Java 5.

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-compiler-plugin</artifactId>
	<executions>
		<execution>
			<goals>
				<goal>testCompile</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<source>1.5</source>
		<target>1.5</target>
		<showDeprecation>true</showDeprecation>
		<showWarnings>true</showWarnings>
	</configuration>






</plugin>

Les éléments showDeprecation et showWarnings sont là pour le jeu de l’intégration continue Hudson qu’on utilise dans le projet. Dans les deux solutions, nous devons ajouter ce mojo pour nous assurer que les annotations TestNG soient bien interprétées par Surefire.

Ajoutons maintenant le plugin Surefire pour exécuter nos tests en phase d’intégration.

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-surefire-plugin</artifactId>
	<executions>
		<execution>
			<phase>integration-test</phase>
			<goals>
				<goal>test</goal>
			</goals>
		</execution>
	</executions>
</plugin>

Ici, on associe simplement l’exécution des tests avec Surefire à la phase d’intégration Maven. Attention dans un projet monolithique, cette démarche ne fonctionne pas de la même façon. En effet, Maven ne supporte qu’un répertoire de sources pour les tests. Vous devrez donc séparer les tests unitaires des tests d’intégrations en différenciant les packages. On peut par exemple utiliser un package fr.mycompany.myapp.it pour contenir nos tests d’intégrations.

Il faut utiliser les mécanismes d’exclusion de Surefire pour la phase de test et la phase de test d’intégration de Maven :

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-surefire-plugin</artifactId>
	<configuration>
		<!--
 Pour les tests unitaires on exclut tous les tests dans it/**
-->
	<excludes>
		<exclude>**/it/**</exclude>
	</excludes>
	</configuration>
	<executions>
		<execution>
			<id>run-it</id>

			<phase>integration-test</phase>
			<goals>
				<goal>test</goal>
			</goals>
			<configuration>
				<!--
Pour les tests d'intégrations on inclut seulement les tests dans it/**
		-->
				<excludes>
					<exclude>*</exclude>
				</excludes>
				<includes>
					<include>**/it/**</include>
				</includes>
			</configuration>
		</execution>
	</executions>
</plugin>

Et maintenant on ajoute le premier test proprement dit. Il devra toujours passer, pour nous indiquer que tout va bien :

// <strong>.it pour un projet monolithique
package fr.mycompany.myapp;

import static org.testng.Assert.</strong>;
import org.testng.annotations.Test;

public class PremierTest {

  @Test
  public void shouldPass() {
    assertTrue(true);
  }
}

Pour vérifier le tout, on peut lancer un mvn install. Le test d’intégration est exécuté avec succès après les tests unitaires.

Cargo

Nous allons maintenant ajouter le plugin Cargo pour installer un serveur Tomcat et y ajouter notre application en déploiement statique. On associe le goal start à la phase Maven pre-integration-test pour démarrer le serveur avant les tests. Le goal stop est associé à la phase post-integration-test maven pour arrêter le serveur après les tests.

<plugin>
	<groupId>org.codehaus.cargo</groupId>
	<artifactId>cargo-maven2-plugin</artifactId>
	<configuration>
		<wait>false</wait>
		<container>
			<containerId>tomcat5x</containerId>
			<zipUrlInstaller>
				<url>http://www.apache.org/dist/tomcat/tomcat-5/v5.5.27/bin/apache-tomcat-5.5.27.zip
				</url>
			</zipUrlInstaller>
		</container>
		<configuration>
			<deployables>
				<deployable>
					<groupId>fr.mycompany.myapp</groupId>
					<artifactId>myapp-webapp</artifactId>
					<type>war</type>
					<properties>
						<context>myapp</context>
					</properties>
				</deployable>
			</deployables>
			<properties>
				<cargo.logging>high</cargo.logging>
				<cargo.servlet.port>9999</cargo.servlet.port>
			</properties>
		</configuration>
	</configuration>
	<executions>
		<execution>
			<id>start</id>
			<phase>pre-integration-test</phase>
			<goals>
				<goal>start</goal>
			</goals>
		</execution>
		<execution>
			<id>stop</id>
			<phase>post-integration-test</phase>
			<goals>
				<goal>stop</goal>
			</goals>

		</execution>
	</executions>
</plugin>

Cette fois le container est de type installed (valeur par défaut) et utilise un zipUrlInstaller se chargeant d’installer le serveur tomcat à partir d’une URL pointant sur le zip de Tomcat 5.5.27.

On aurait aussi pu passer par une installation existante de Tomcat en utilisant un tag

<home>chemin répertoire d'installation</home

à la place du zipUrlInstaller.

Attention le Deployable utilise l’artefact myapp-webapp qui doit être ajouté dans les dépendances du module myapp-functional-tests.
Notez aussi que le conteneur étant de type local, Cargo utilise un déploiement statique sans avoir besoin de décrire le Deployer.

Dans un projet monolithique, on n’aura besoin de fournir ni le groupId, ni l’artefactId.

Pour tester on peut lancer mvn install. Dans la sortie de Maven on verra les traces de démarrage et d’arrêt du serveur Tomcat avec le déploiement de l’application myapp.

Selenium

Tout est prêt pour commencer à vraiment tester notre application avec un navigateur internet. Pour pouvoir utiliser Selenium dans nos tests on doit ajouter le dépôt maven OpenQA à notre pom parent.

<repository>
	<id>OpenQA</id>
	<url>http://archiva.openqa.org/repository/releases/</url>
</repository>

Cette fois, nous avons besoin du mojo selenium-maven-plugin qui permet de lancer et de stopper le serveur Selenium.

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>selenium-maven-plugin</artifactId>
	<executions>
		<execution>
			<id>server</id>
			<phase>pre-integration-test</phase>
			<goals>
<!--


Si besoin d'xvfb
						<goal>xvfb</goal>
	-->
				<goal>start-server</goal>
			</goals>
			<configuration>
				<background>true</background>
			</configuration>
		</execution>
		<execution>
			<id>stop</id>
			<phase>post-integration-test</phase>
			<goals>
				<goal>stop-server</goal>
			</goals>
		</execution>
	</executions>
</plugin>

Nous avons simplement associé le goal start-server à la phase pre-integration-test pour lancer le serveur Selenium avant d’exécuter les tests d’intégrations. Le goal stop-server est associé à la phase post-integration-test pour arrêter le serveur après l’exécution des tests.

Pour ceux qui ont un serveur d’intégration tournant sous linux vous voudriez sans doute décommenter le goal xvfb pour fournir un environnement graphique virtuel à Selenium.

Avant de pouvoir utiliser une session Selenium dans nos tests fonctionnels, on doit ajouter l’API cliente dans les dépendances de notre module.

<dependency>
	<groupId>org.openqa.selenium.client-drivers</groupId>
	<artifactId>selenium-java-client-driver</artifactId>
	<version>1.0-beta-1</version>
	<scope>test</scope>
</dependency>

Ajoutons maintenant une classe pour tester le tout dans notre module de tests ou dans le package fr.mycompany.myapp.it pour un projet simple.

package fr.mycompany.myapp;


import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;

@Test
public class WebappTest
{

	private Selenium selenium;

	@BeforeTest
	public void beforeTests(){
		selenium = new DefaultSelenium("localhost",4444, "*firefox", "http://localhost:9999/myapp");
		selenium.start();
	}

	@AfterTest
	public void afterTests(){
		selenium.stop();
		selenium = null;
	}

	@Test
    public void testCallIndexPage() throws Exception
    {
    	selenium.open("http://localhost:9999/myapp/index.jsp");
    	selenium.waitForPageToLoad("5000");
    	Assert.assertTrue(selenium.isTextPresent("Hello World!"));
    	Assert.assertEquals("Hello", selenium.getTitle());
    }
}

Pour tester cette dernière étape, il suffit de lancer mvn install. Notre projet supporte maintenant l’exécution de tests fonctionnels automatisée.
À partir de notre serveur d’intégration, nous pouvons maintenant déployer un build stable sur notre plateforme de qualification et exécuter des tests de non-régressions fonctionnelles.

Conclusion

Si on passe la pauvreté de la documentation concernant Maven, avec son API multi-conteneurs, Cargo simplifie la mise en place des interactions avec le conteneur choisi. Il peut aussi s’avérer très utile pour des tests de migrations. Tout cela pourrait être magique, mais il reste de grands absents parmi les serveurs J2EE du marché comme Websphere, Weblogic 10, et Glassfish.

D’autant que les containers supportés n’implémentent pas forcément l’intégralité de l’API. Après être tombé en désuétude pendant un an sans aucune nouvelle sur leur page principale, Cargo semble avoir repris ses activités. En effet, la version 1.0-alpha-6 est disponible depuis septembre sur le dépot maven de Codehaus . Parmi les nouveautés on trouve Weblogic 9, Tomcat 6 et JBoss 5. Après une succession de versions alpha en septembre, Cargo est disponible depuis le 22 octobre en beta-1 dans vos dépôts Maven.

Sources des projets de cet article :

Références :

One Response

Laisser un commentaire