I. Introduction▲
La complexité croissante des projets applicatifs actuels implique la mise en place d'usines de développement. Ces « usines » permettent de limiter les tâches répétitives pour les développeurs en leur fournissant un système complet d'intégration automatique des différents éléments d'un projet.
Plus concrètement, certains projets possèdent des dizaines de développeurs et la taille de l'application implique parfois des heures de compilation. De plus, si des tests unitaires sont couplés à ce processus, la mise en place d'un serveur d'intégration continue s'avère essentielle pour la qualité d'exécution d'un tel projet.
Voici un exemple de principe d'intégration continue qui reprend certains aspects énoncés par les méthodes dites « agiles » :
- L'équipe de développement programme la journée sur un projet en utilisant un outil de contrôle de version (CVS, Subversion, VSS, ClearCase…) ;
- Un développement est terminé pendant la journée, le développeur concerné le met à jour dans le contrôle de version (checkin) ;
- Le système d'intégration continue détecte la mise à jour des sources et lance une compilation incrémentale (qui n'implique que le bloc de code mis à jour) ;
- La journée se termine ;
- La nuit, à heure fixe, une compilation complète du code est démarrée grâce au système d'intégration continue. On appelle aussi ce processus le nightlybuild ;
- La compilation se termine, une série de tests unitaires sont lancés pour valider la nouvelle version compilée ;
- Le matin, le chef de projet et les développeurs ont accès à un reporting convivial sur les résultats du nightlybuild.
Cet article a pour objet de réaliser la mise en place pas à pas d'un système d'intégration continue grâce à l'outil CruiseControl.NET qui se base sur le framework .NET.
II. Exemple simple de processus d'intégration continue▲
Le processus décrit dans l'introduction est un peu trop complexe à mettre en place dans le cadre de cet article, nous allons donc le réduire à effectuer des compilations complètes de l'application dès qu'une modification est détectée. La faible taille de l'application d'exemple le permet. Cette application s'appellera EasyCalc.
- Le développeur enregistre une modification sur le serveur principal (on suppose que le développement se fait avant sur son poste).
- Vérification, à intervalle régulier par le serveur CruiseControl.NET, d'une modification des sources en utilisant les données du système de fichier (pas de contrôle de version).
- Une modification est détectée (sinon, rien ne se passe ensuite) alors le script NAnt est lancé (étape 4).
- Compilation de toutes les sources du projet si une modification est détectée suivit de tests unitaires du nouveau binaire avec Nunit.
- Génération d'un reporting de résultats.
- Consultation des résultats et suivi de l'évolution du build par l'équipe de projet.
Dans le chapitre suivant, nous allons créer les sources de notre application EasyCalc ainsi que les tests unitaires associés. Ensuite, nous écrirons le script NAnt qui sera lancé à chaque modification détectée. Et nous terminerons par l'installation de CruiseControl.NET pour l'orchestration générale.
L'ensemble des outils utilisés seront open source. Ce choix ne provient pas d'une raison de coût ou de conviction, mais uniquement parce les frameworks utilisés sont leader dans leur domaine (NAnt et NUnit).
De plus, ces outils venant du monde Java, les explications techniques fournies s'adapteraient très bien à un projet fait avec le langage de Sun.
III. Création des sources de notre application▲
Pour mettre en place notre système d'intégration continue, nous avons besoin d'une application à compiler et à tester.
Nous allons créer le projet EasyCalc qui sera volontairement simpliste. En dehors de ce contexte d'apprentissage, il n'aurait que peu d'intérêt à être intégré dans une usine de build (compilation).
Nous pourrions utiliser Visual Studio pour le développement, mais nous allons nous contenter de créer les fichiers sources. La compilation sera ensuite faite avec un script Nant.
Les sources sont en C#, mais comme le code n'est pas essentiel dans cet article, si vous développez en VB.NET, les principes sont les mêmes.
EasyCalc se découpe en trois projets principaux :
- la librairie EasyCalcLib.dll qui constitue le cœur de l'application ;
- une application console (Easycalc.exe) qui se charge uniquement d'interfacer EasyCalcLib.dll avec l'utilisateur ;
- une librairie de tests unitaires, TestCalc.dll qui utilise Nunit.
L'application effectuera au choix une des quatre opérations élémentaires sur deux nombres passés en paramètres. Voici un exemple :
« EasyCalc.exe 2 3 + »
Affichera : « Le résultat de l'opération est 5 »
Pour commencer, créez un répertoire qui contiendra les sources, par exemple : C:\ArticleCC\Sources
III-A. EasyCalcLib▲
Créez un sous-répertoire nommé C:\ArticleCC\Sources\EasyCalcLib
Placez dans ce répertoire, un fichier Calc.cs qui contient le code suivant :
using
System;
namespace
EasyCalcLib
{
public
class
Calc
{
string
[]
args;
public
Calc
(
string
[]
args)
{
this
.
args =
args;
}
public
double
Calculer
(
)
{
if
(
args ==
null
||
args.
Length !=
3
)
throw
new
Exception
(
"Le nombre d'arguments est invalide"
);
try
{
double
resultat;
switch
(
args[
2
]
)
{
case
"+"
:
resultat =
addition
(
Int32.
Parse
(
args[
0
]
),
Int32.
Parse
(
args[
1
]
));
break
;
case
"-"
:
resultat =
soustraction
(
Int32.
Parse
(
args[
0
]
),
Int32.
Parse
(
args[
1
]
));
break
;
case
"*"
:
resultat =
multiplication
(
Int32.
Parse
(
args[
0
]
),
Int32.
Parse
(
args[
1
]
));
break
;
case
"/"
:
resultat =
division
(
Int32.
Parse
(
args[
0
]
),
Int32.
Parse
(
args[
1
]
));
break
;
default
:
throw
new
Exception
(
"Opérateur inconnu"
);
}
return
resultat;
}
catch
(
Exception ex)
{
throw
new
Exception
(
"Erreur pendant le calcul : "
+
ex.
Message);
}
}
private
int
addition
(
int
nb1,
int
nb2)
{
return
nb1 +
nb2;
}
private
int
soustraction
(
int
nb1,
int
nb2)
{
return
nb1 -
nb2;
}
private
int
multiplication
(
int
nb1,
int
nb2)
{
return
nb1 *
nb2;
}
private
double
division
(
int
nb1,
int
nb2)
{
return
Math.
Round
((
double
)nb1 /
nb2,
2
);
// arrondis à 2 décimales
}
}
}
III-B. EasyCalc▲
Créez un sous-répertoire nommé C:\ArticleCC\Sources\EasyCalc
Placez dans ce répertoire, un fichier MainClass.cs qui contient le code suivant :
using
System;
using
EasyCalcLib;
namespace
EasyCalc
{
class
MainClass
{
[STAThread]
static
void
Main
(
string
[]
args)
{
Calc calc =
new
Calc
(
args);
Console.
WriteLine
(
String.
Format
(
"Le résultat de l'opération est : {0}"
,
calc.
Calculer
(
))
);
Console.
ReadLine
(
);
}
}
}
III-C. TestCalc▲
Il s'agit de la librairie de tests unitaires. Elle utilise les classes du framework NUnit qui est inclus dans l'installation de NAnt. Il est donc inutile de l'installer sauf si vous désirez profiter des différents outils fournis avec l'installateur de Nunit.
Pour en savoir plus sur NUnit, vous pouvez consulter l'article suivant :
Les tests unitaires avec NUnit : https://jab.developpez.com/tutoriels/dotnet/nunit/
Les tests unitaires font partie intégrante des systèmes d'intégration continue, car ils permettent de valider la bonne qualité d'une application, si vous ne connaissez pas ces principes, mieux vaut consulter l'article cité avant de continuer.
Créez un sous-répertoire nommé C:\ArticleCC\Sources\TestCalc
Placez dans ce répertoire, un fichier TestClass.cs qui contient le code suivant :
using
System;
using
NUnit.
Framework;
using
EasyCalcLib;
namespace
TestCalc
{
[TestFixture]
public
class
TestClass
{
[Test]
public
void
Addition
(
)
{
Calc calc =
new
Calc
(
new
string
[]
{
"3"
,
"5"
,
"+"
}
);
Assert.
AreEqual
(
8
,
calc.
Calculer
(
));
}
[Test]
public
void
Soustraction
(
)
{
Calc calc =
new
Calc
(
new
string
[]
{
"4"
,
"1"
,
"-"
}
);
Assert.
AreEqual
(
3
,
calc.
Calculer
(
));
}
[Test]
public
void
Multiplication
(
)
{
Calc calc =
new
Calc
(
new
string
[]
{
"3"
,
"3"
,
"*"
}
);
Assert.
AreEqual
(
9
,
calc.
Calculer
(
));
}
[Test]
public
void
DivisionEntiere
(
)
{
Calc calc =
new
Calc
(
new
string
[]
{
"12"
,
"3"
,
"/"
}
);
Assert.
AreEqual
(
4
,
calc.
Calculer
(
));
}
[Test]
public
void
Division2Decimales
(
)
{
Calc calc =
new
Calc
(
new
string
[]
{
"9"
,
"8"
,
"/"
}
);
Assert.
AreEqual
(
1
.
12
,
calc.
Calculer
(
));
}
[Test]
public
void
DivisionParZero
(
)
{
Calc calc =
new
Calc
(
new
string
[]
{
"4"
,
"0"
,
"/"
}
);
Assert.
AreEqual
(
Double.
PositiveInfinity,
calc.
Calculer
(
));
}
}
}
Les tests définis ici passent tous avec succès si les sources données précédemment sont respectées. Nous le vérifierons dans la suite de l'article.
IV. Écriture du script NAnt▲
NAnt est un outil de permettant de définir au format xml une série de tâches successives. Au départ utilisé pour la compilation indépendamment de l'environnement de développement, ses tâches ont été étendues à de nombreux autres usages.
Si vous ne connaissez pas NAnt, il est préférable, avant de continuer, de consulter l'article suivant (en ignorant la partie Visual Studio .NET si besoin) :
Débuter avec NAnt sous Visual Studio.NET : https://defaut.developpez.com/tutoriel/dotnet/nant/
L'application EasyCalc est développée dans la technologie .NET, mais ce n'est pas d'une obligation. Vous pouvez, en effet, utiliser NAnt avec d'autres compilateurs.
- Rendez-vous sur http://nant.sourceforge.net/ et téléchargez la dernière version de Nant (0.85 RC3 au moment d'écrire cet article).
- Décompressez l'archive récupérée dans le répertoire de votre choix. Nous supposerons que le répertoire de NAnt choisi est C:\Program Files\nant\
- Créez le fichier EasyCalc.build dans le répertoire C:\ArticleCC\Sources et placez-y le script suivant :
<?xml version="1.0"?>
<project
name
=
"EasyCalc"
default
=
"test"
basedir
=
"."
xmlns
=
"http://nant.sf.net/release/0.85-rc3/nant.xsd"
>
<description>
Compilation et tests de EasyCalc.</description>
<property
name
=
"workingDir"
value
=
"C:\ArticleCC"
/>
<property
name
=
"sourcesDir"
value
=
"${workingDir}\Sources"
/>
<tstamp
property
=
"builddate"
pattern
=
"yyyyMMdd_HHmm"
/>
<property
name
=
"outputDir"
value
=
"${workingDir}\Binaries\EasyCalc-${builddate}"
/>
<property
name
=
"testsResultDir"
value
=
"${workingDir}\Binaries"
/>
<target
name
=
"build"
description
=
"Compilation des sources de EasyCalc"
>
<mkdir
dir
=
"${outputDir}"
failonerror
=
"false"
/>
<csc
target
=
"library"
output
=
"${outputDir}\EasyCalcLib.dll"
debug
=
"false"
>
<sources>
<include
name
=
"${sourcesDir}\EasyCalcLib\*.cs"
/>
</sources>
</csc>
<csc
target
=
"exe"
output
=
"${outputDir}\EasyCalc.exe"
debug
=
"false"
>
<sources>
<include
name
=
"${sourcesDir}\EasyCalc\*.cs"
/>
</sources>
<references>
<include
name
=
"${outputDir}\EasyCalcLib.dll"
/>
</references>
</csc>
<csc
target
=
"library"
output
=
"${outputDir}\TestCalc.dll"
debug
=
"false"
>
<sources>
<include
name
=
"${sourcesDir}\TestCalc\*.cs"
/>
</sources>
<references>
<include
name
=
"${outputDir}\EasyCalcLib.dll"
/>
<include
name
=
"C:\Program Files\nant\bin\lib\net\1.1\nunit.framework.dll"
/>
</references>
</csc>
</target>
<target
name
=
"test"
description
=
"Test de EasyCalc"
depends
=
"build"
>
<nunit2>
<formatter
type
=
"Xml"
usefile
=
"true"
extension
=
".xml"
outputdir
=
"${testsResultDir}"
/>
<test
assemblyname
=
"${outputDir}\TestCalc.dll"
/>
</nunit2>
</target>
</project>
Le script se découpe en trois parties :
IV-A. La définition des propriétés▲
- workingDir : le répertoire principal contenant tous les fichiers de notre projet.
- sourcesDir : le répertoire contenant les sources de EasyCalc.
- outputDir : le répertoire qui contiendra les nouveaux fichiers binaires (il sera unique pour chaque compilation et permettra d'historiser les données).
- testsResultDir : le répertoire qui recevra le log Nunit.
La compilation (« build »)
Trois compilations pour trois projets sont effectuées :
- EasyCalcLib dont dépendent les deux autres projets ;
- EasyCalc, l'application console ;
- La dll de tests, TestCalc.dll qui dépend du framework NUnit. Ici la référence est faite vers le NUnit inclus dans Nant.
Les tests unitaires (« tests »)
La tâche <nunit2> est appelée en spécifiant d'écrire un fichier de log des tests au format xml. Ainsi CruiseControl pourra le récupérer et afficher les informations. On veille aussi bien à spécifier la dll contenant la série de tests.
Cette task est dépendante de « build » et ne se fera pas si cette dernière échoue.
V. Installation et configuration de CruiseControl.NET▲
V-A. Installation▲
CruiseControl.NET contient trois éléments principaux :
- le serveur qui est le centre du système et qui se charge de gérer l'ensemble du processus d'intégration continue ;
- le webdashboard, une interface web permettant de consulter les différents projets répartis sur un ou plusieurs serveurs. Dans notre cas, nous aurons un projet branché sur un serveur ;
- le cctray, un outil de monitoring symbolisé par une petite icône dans la traybar indiquant le statut du ou des projets en cours (rouge, jaune ou vert pour signifier : échec, compilation en cours ou succès).
Téléchargez l'installateur principal sur le site du projet (version 1.0 RC2 au moment d'écrire cet article) : http://ccnet.thoughtworks.com/.
Vérifiez que IIS est installé sur votre machine avec le support d'ASP.NET.
Installez CruiseControl.NET en décochant la possibilité de lancer le serveur en tant que service (ce n'est pas utile dans le cadre de cet article). Le répertoire d'installation devrait être C:\Program Files\CruiseControl.NET.
Vérifiez que vous avez accès au DashBoard en tapant dans votre navigateur : http://127.0.0.1/ccnet
Si tout va bien, vous devriez voir apparaître cette interface :
Le message d'erreur est normal puisque nous n'avons pas encore démarré le serveur.
Téléchargez le projet CCTray qui est indépendant du projet principal et installez-le.
V-B. Configuration▲
Ouvrez le fichier C:\Program Files\CruiseControl.NET\server\ccnet.config
Ce fichier contient la définition des projets CruiseControl et la quasi-totalité de la configuration du serveur. Le site de l'éditeur détaille sa structure et ses possibilités, n'hésitez pas à le consulter.
Nous allons créer un nœud <project/> pour y définir notre projet EasyCalc.
Copiez dans ccnet.config, le xml suivant :
<cruisecontrol>
<project>
<name>
EasyCalc</name>
<webURL>
http://localhost/ccnet-dashboard/ ?_action_ViewProjectReport=true&
server=local&
project=EasyCalc</webURL>
<triggers>
<intervalTrigger
seconds
=
"60"
/>
</triggers>
<workingDirectory>
.</workingDirectory>
<modificationDelaySeconds>
2</modificationDelaySeconds>
<sourcecontrol
type
=
"filesystem"
>
<repositoryRoot>
C:\ArticleCC\Sources</repositoryRoot>
</sourcecontrol>
<tasks>
<nant>
<executable>
C:\Program Files\nant\bin\nant.exe</executable>
<buildFile>
C:\ArticleCC\Sources\EasyCalc.build</buildFile>
<targetList>
<target>
test</target>
</targetList>
</nant>
</tasks>
<publishers>
<merge>
<files>
<file>
C:\ArticleCC\Binaries\*-results.xml</file>
</files>
</merge>
<xmllogger />
</publishers>
</project>
</cruisecontrol>
Les actions effectuées sont les suivantes :
- <triggers/> permet ici de définir un intervalle entre chaque vérification des sources, 60 secondes dans notre cas ;
- <sourcecontrol/> permet de choisir le système de contrôle des modifications (souvent CVS, VSS, ClearCase…). Ici nous nous baserons uniquement sur une lecture du système de fichier grâce aux dates des fichiers. Le répertoire à monitorer est défini dans <repositoryRoot/> ;
- <tasks> décrit les tâches à effectuer quand une modification est détectée. Il s'agit, dans notre exemple, de lancer la tâche « test » du script NAnt qui se chargera ensuite de lancer la compilation et les tests.
- <publishers/> permet de récupérer les logs générés (seul NUnit ici) et ensuite d'appeler le qui créera le log CruiseControl de l'intégration courante.
Ce log sera placé ensuite dans ce répertoire : C:\Program Files\CruiseControl.NET\server\EasyCalc\Artifacts\buildlogs.
Ce répertoire n'existe pas jusqu'à la première intégration effectuée. L'ensemble de ces fichiers de logs seront parsés et mis en page grâce à des feuilles xsl se trouvant dans C:\Program Files\CruiseControl.NET\webdashboard\xsl
V-C. Premier démarrage et cctray▲
Démarrez-le CCTray précédemment installé, il apparaît gris dans la barre des tâches. En effet, aucun serveur n'y est configuré.
Faites un clic droit dessus puis « Settings ».
Dans la partie « BuildServer », cliquez sur « Add » et ajoutez le serveur « 127.0.0.1 » et son projet « EasyCalc ».
Exécutez C:\Program Files\CruiseControl.NET\server\ccnet.exe
Si tout se passe bien, quatre nouveaux fichiers devraient être détectés et une intégration démarrée.
Cette information permet de comprendre que toute la compilation et les tests se sont bien passés.
Si au moins un test échoue, le cctray apparaît en rouge (pour l'éviter, il faudrait modifier le script NAnt avec un failonerror à false pour les tests nunit, nous ne le testerons pas ici).
Pour essayer cela, provoquons un échec du test unitaire « Division2Decimal » :
Dans le fichier C:\ArticleCC\Sources\EasyCalcLib\Calc.cs, modifiez la ligne :
return
Math.
Round
((
double
)nb1 /
nb2,
2
);
// arrondis à 2 décimales
Par :
return
Math.
Round
((
double
)nb1 /
nb2,
3
);
// arrondis à 3 décimales
Enregistrez le fichier. Au bout de quelque temps (60 secondes maximum), le serveur CruiseControl va détecter la modification et lancer l'intégration. À la fin, l'intégration apparaîtra en échec.
Rappelons qu'à chaque compilation un nouveau répertoire est créé par notre script NAnt. L'ensemble des binaires se trouvent dans C:\ArticleCC\Binaries.
V-D. Le dashboard▲
Pour analyser, la raison de l'échec précédent, ouvrons-le Dashboard : http://127.0.0.1/ccnet.
Cliquez sur EasyCalc puis sur le dernier résultat en échec dans le menu de gauche. Un écran ressemblant à ceci devrait apparaître :
À gauche, apparaît la liste des derniers builds effectués ainsi qu'un menu permettant d'atteindre les différents sous-rapports.
Au centre, nous trouvons des informations concernant l'intégration. On trouve le nom des fichiers modifiés ainsi que la raison de l'échec : « Tests failed » suivis du détail du test échoué (le test Division2Decimales).
Pour plus d'informations concernant les tests et le log du script NAnt, vous pouvez consulter les parties concernant NAnt et NUnit sur le menu de gauche.
Sachez que le dashboard est paramétrable. Vous pouvez éditer le fichier C:\Program Files\CruiseControl.NET\webdashboard\dashboard.config pour y relier d'autres serveurs CruiseControl.NET, mais aussi pour configurer les différents éléments de l'interface.
Consultez le site de l'éditeur pour plus de précisions : http://ccnet.thoughtworks.com.
VI. Conclusion▲
Le principe d'intégration continue est intéressant pour de nombreux projets naissant ou existant depuis longtemps. Il permet, en plus d'automatiser, de valider la qualité d'une application et d'informer, non seulement les développeurs, mais aussi le client ou la maîtrise d'ouvrage du projet. La convivialité et les capacités de personnalisation de l'interface web fournie avec CruiseControl.NET permettent effectivement d'obtenir un rendu accessible à tous les maillons d'un projet.
De plus, CruiseControl.NET a la capacité de s'interfacer avec la plupart des outils du marché. Nous avons utilisé ici NAnt et NUnit, mais MSBuild, FXCop, MBUnit, NCover et autres outils de compilation, de tests ou d'audit de code sont intégrables facilement.
L'arrivée de TeamSystem de Microsoft risque de changer les choses, mais CruiseControl.NET a une bonne carte à jouer pour se faire une place dans le monde de l'intégration continue. La raison ? Il est solide et open source.
Téléchargez la version PDF de cet article.
VII. Remerciements▲
Merci à Isabelle et Jean-Jacques de m'avoir fait découvrir les principes et les outils liés aux usines de build et de tests.
Merci à Pedro204 pour la relecture.