I. Introduction▲
Ce qui suit est un TP de Génie Logiciel qui utilise Java. Notez qu'on aurait pu mettre en application les concepts de GL, comme les design patterns, les notions OO, l'usine logiciel (etc.) avec un autre langage sans changer beaucoup de choses. Ce TP peut sembler un peu long en première approche (n'oubliez pas que vous devez le finir à la maison si vous n'avez pas fini à l'école).
Vous allez coder et tester un DAO (Data Access Object) en Java sous Eclipse. Vous utiliserez Maven pour construire votre application. Vous écrirez des classes Java, mais aussi des interfaces et des enums. Vous dessinerez des diagrammes UML. Vous allez manipuler des ressources en Java, gérer des exceptions, décoder des lignes, alimenter des listes. Vous tracerez vos méthodes à l'aide de Log4J. Vous utiliserez des bibliothèques comme OpenCsv ou CsvEnine pour lire des données dans des fichiers. Et bien entendu, vous aurez des travaux (identifiés par une icône en forme de calepin) à réaliser lors des étapes-clés et à rendre à la fin de la séance.
Des pistes et/ou des solutions sont proposées en annexe. Essayez de résoudre les problèmes par vous-même avant de regarder les réponses.
Demandez l'aide du professeur ou de son assistant si vous restez bloqué trop longtemps. Pensez tout de même à consulter la FAQ en annexe.
Si vous souhaitez installer Maven et Java sur votre ordinateur portable, vous pouvez consulter les FAQ en annexe. Vous y trouverez les points essentiels.
I-A. Versions des logiciels et bibliothèques utilisés▲
Pour écrire ce document, j'ai utilisé les versions suivantes :
- Java JDK 1.8 ;
- Eclipse Indigo 3.7 JEE 64b ;
- Maven 3.0.3 ;
- Log4J 1.2.13 ;
- JUnit 4.12 ;
- Open CSV 2.3 ;
- CSV Engine 1.3.5.
I-B. Mises à jour▲
1er décembre 2012 : Création.
6 mars 2013 : Ajout de questions dans la FAQ. Ajout de liens. Ajout d'un petit manuel d'installation à la maison.
14 avril 2014 : Ajout d'une vidéo pour illustrer les premières étapes.
1er septembre 2014 : Les travaux sont à désormais à rendre : une copie par élève (ou binôme).
22 novembre 2014 : Ajout d'une vidéo pour illustrer les premières étapes avec Eclipse Luna, quand on a le plugin Maven (m2).
1er novembre 2016 : Remplacement de tout le chapitre dédié à l'import dans Eclipse et à la préparation Maven.
II. Préparation▲
Avant d'entrer dans le vif du sujet, vous devez suivre quelques étapes simples (mais intéressantes) pour préparer le travail.
II-A. Télécharger le projet d'exemple▲
Durée estimée : 1 minute.
Pour commencer, s'il ne vous a pas été fourni par votre professeur, téléchargez le fichier "tp-chien-dao-01.zip" (15ko)Fichier tp-chien-dao-01.zip (16ko), contenant un projet Java-Maven d'exemple.
Décompressez le fichier "tp-chien-dao-01.zip".
Vous devez normalement avoir un dossier avec les éléments suivants :
- le dossier "src" où se trouvent les sources ;
- le fichier "pom.xml" pour Maven ;
- les fichiers "build.bat" et "build.sh" ;
- les fichiers "license.txt" et "README.txt" (ces deux derniers fichiers ne sont pas importants pour la suite).
Voici deux captures d'écran pour vous montrer à quoi cela ressemble sur MON ordinateur portable :
II-B. Import dans Eclipse (projet Maven)▲
Durée estimée : 2 minutes.
Importez le projet directement dans Eclipse car cet IDE sait traiter les projets Maven. Pour cela, lancez donc Eclipse si ce n'est pas déjà fait. Utilisez le menu "File/Import". Dans la popup il faut prendre "Maven/Existing Maven projet". Dans la seconde popup, utilisez le bouton "Browse" pour aller jusqu'au dossier que vous avez dézippé et où se trouve le fichier "pom.xml". Enfin cliquez sur le bouton "Finish". Eclipse devrait mouliner quelques instants.
Le fichier Maven "pom.xml" décrit la structure du projet d'exemple. Maintenant que le projet a été importé dans Eclipse, vous pouvez l'ouvrir directement. Vous auriez pu l'ouvrir avec Ultra Edit, VI, ou tout autre éditeur de code, mais pas avec Internet Explorer ou Word hein !... Voici un résumé des parties importantes à ce stade.
<project
xmlns
=
"http://maven.apache.org/POM/4.0.0"
...>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.icauda</groupId>
<artifactId>
tp-chien-dao</artifactId>
<version>
1.0-SNAPSHOT</version>
<packaging>
jar</packaging>
<name>
TP Chien</name>
<properties>
<!-- Construction du projet -->
<project.build.sourceEncoding>
UTF-8</project.build.sourceEncoding>
<java.version>
1.8</java.version>
<maven-compiler-plugin.version>
3.6.0</maven-compiler-plugin.version>
<!-- Lib de test -->
<junit.version>
4.12</junit.version>
<!-- Lib de log -->
<log4j.version>
1.2.13</log4j.version>
...
</properties>
<dependencies>
<!-- Junit -->
<dependency>
<groupId>
junit</groupId>
<artifactId>
junit</artifactId>
<version>
${junit.version}</version>
<scope>
test</scope>
</dependency>
<!-- log4j -->
<dependency>
<groupId>
log4j</groupId>
<artifactId>
log4j</artifactId>
<version>
${log4j.version}</version>
</dependency>
</dependencies>
...
</project>
Le fichier "pom.xml" indique ici une dépendance aux bibliothèques "jUnit" en version "4.12" et "Log4j" en version "1.2.13".
Les versions choisies ("4.12" et "1.2.13") sont tout simplement les dernières qui étaient disponibles quand j'ai écrit (ou mis à jour) ce document.
Dans la fenêtre principale d'Eclipse, vous pouvez distinguer la structure des projets Java-Maven :
- les dossiers "src/test/java" et "src/test/resources" contiennent respectivement des tests écrits en Java et des fichiers de configuration (ressources) pour les tests ;
- les dossiers "src/main/java" et "src/main/resources" contiennent le code du programme et les ressources associées ;
- le bloc "Referenced librairies" contient la liste des dépendances (bibliothèques) utilisées par le projet. Ce sont ces mêmes dépendances qui apparaissent dans le fichier "pom.xml" ;
- le bloc "JRE System Librairy" correspond simplement à la version de Java que vous utilisez ;
- enfin le dossier "target" (déjà présenté plus haut) contient la version compilée par Maven du projet.
Ouvrez la classe "Launcher" que vous trouverez dans le package "com.icauda.tp.chien". Cette classe contient une méthode "main(..)" et est donc exécutable.
Lancez l'exécution de la classe "Launcher". Pour cela, sélectionnez la classe puis cliquez sur l'icône en forme de triangle (cf. capture). Si tout est bon, vous devriez voir apparaître des lignes dans la "console".
Vous pouvez aussi lancer l'exécution à l'aide du bouton droit de la souris. Dans le menu contextuel qui apparaît, choisissez "Run as/Java application".
II-C. Quelques raccourcis utiles▲
Avant d'aller plus loin, voici quelques raccourcis qui devraient vous être utiles pour la suite.
Navigation : [F3] : Dans le programme, positionnez-vous sur une variable, une méthode ou une classe puis cliquez sur [F3] pour aller directement à la déclaration de l'élément.
Par exemple, dans la méthode "main(..)" de la classe "Launcher", cliquez sur "doFindChiensBidon" (ligne 39) puis tapez la touche [F3]. Ça vous emmènera à la déclaration de la méthode "doFindChiensBidon()" à la ligne 45.
Un autre exemple, maintenant que vous êtes sur la méthode "doFindChiensBidon()", cliquez sur "BidonChienDao" (ligne 49) puis tapez sur [F3]. Ça ouvre la classe "BidonChienDao" et ça vous positionne au niveau de la déclaration.
Formatage : [Ctrl] + [Shift] + [F] : Vous pouvez/devez formater le code régulièrement. Cette combinaison de touche indente votre programme en fonction des paramètres configurés dans Eclipse.
Des fois, quand vous cherchez une erreur dans votre code, le simple fait de le formater vous aide à la détecter.
Vous devez formater le code avant de demander l'aide du professeur, pour qu'il y voie clair.
Import [Ctrl] + [Shift] + [M] : Cette combinaison vous permet d'importer une classe ou de faire un import statique. Pour cela, il faut cliquer sur l'élément, puis faire la combinaison de touches.
Organize imports [Ctrl] + [Shift] + [O] : Cette combinaison fait le ménage dans tous les imports. En particulier, elle supprime les imports de classe inutiles (i.e. importées, mais pas utilisées).
Pour en finir avec les raccourcis de votre IDE préféré, je vous propose un article plus détaillé, et qui vous permettra notamment de télécharger un petit mémento des raccourcis d'Eclipse.
II-D. Les objets du domaine▲
Durée estimée : 5 minutes.
Il est temps de découvrir les objets que vous allez utiliser dans ce TP. Sans surprise, vous avez sûrement déjà deviné que ça parle de chiens.
Le cœur du domaine est constitué par l'interface Chien, qui définit quelques méthodes simples :
public
interface
Chien extends
Serializable {
String getNom
(
);
String getNomComplet
(
);
Sexe getSexe
(
);
RaceDeChien getRace
(
);
List<
String>
getCouleurs
(
);
Double getPoids
(
);
}
La classe SimpleChien est l'implémentation (par défaut) de Chien.
public
class
SimpleChien implements
Chien {
private
String nom;
private
String nomComplet;
private
Sexe sexe;
private
RaceDeChien race;
private
List<
String>
couleurs;
private
Double poids;
public
SimpleChien
(
) {
// rien...
}
public
SimpleChien
(
final
String nom) {
this
.nom =
nom;
}
public
SimpleChien
(
final
String nom, final
RaceDeChien race, final
Sexe sexe) {
this
(
nom);
this
.race =
race;
this
.sexe =
sexe;
}
@Override
public
String toString
(
) {
return
"SimpleChien [nom="
+
nom +
"]"
;
}
// Getters et setters
...
}
Comme vous pouvez le constater, SimpleChien (et Chien) définit des attributs vers les enums Sexe et RaceDeChien, dont l'utilité ne devrait pas nécessiter d'explication.
public
enum
Sexe {
FEMALE
(
2
),
MALE
(
1
);
private
final
int
code;
Sexe
(
final
int
code) {
this
.code =
code;
}
public
static
Sexe valueOfByCode
(
final
int
code) {
switch
(
code) {
case
2
: return
FEMALE;
case
1
: return
MALE;
default
:
throw
new
IllegalArgumentException
(
"Le sexe demande n'existe pas."
);
}
}
public
boolean
isMale
(
) {
return
this
==
MALE;
}
public
int
getCode
(
) {
return
code;
}
}
public
enum
RaceDeChien {
BASSET_ALPES
(
"Basset des Alpes"
, "basset_alp"
),
BERGER_ALLEMAND
(
"Berger allemand"
, "berger_all"
),
CANICHE
(
"Caniche"
, "caniche"
),
HARRIER
(
"Harrier"
, "harrier"
),
GOLDEN
(
"Golden retriever"
, "golden_ret"
),
ROTTWEILER
(
"Rottweiler"
, "rottweiler"
),
WESTIE
(
"Westie"
, "westie"
);
private
final
String label;
private
final
String code;
RaceDeChien
(
final
String label, final
String code) {
this
.label =
label;
this
.code =
code;
}
public
static
RaceDeChien valueOfByCode
(
final
String code) {
// On verifie que le code n'est ni nul ni vide.
if
(
code ==
null
||
code.isEmpty
(
)) {
throw
new
IllegalArgumentException
(
"Le code ne peut pas etre vide."
);
}
// Note : La methode "values()" renvoie la liste de toutes les
// "instances" ce cette enum (ie. BASSET_ALPES, CANICHE, HARRIER, etc.)
for
(
RaceDeChien race : values
(
)) {
if
(
race.code.equalsIgnoreCase
(
code)) {
return
race;
}
}
// Si on n'a pas trouve alors on lance une exception.
throw
new
IllegalArgumentException
(
"La race de chien demandee n'existe pas."
);
}
public
String getLabel
(
) {
return
label;
}
public
String getCode
(
) {
return
code;
}
}
Faites un diagramme de classe UML, incluant les quatre objets du domaine. Ce travail est à faire au stylo et à rendre à la fin de la séance.
II-E. DAO pour la lecture▲
Durée estimée : 5 minutes.
Le DAO (Data Access Object) ChienDao est également fourni avec le zip. Il définit la méthode "findAllChiens()" sur laquelle vous allez travailler dans la suite.
public
interface
ChienDao {
List<
Chien>
findAllChiens
(
);
}
Pour commencer, et pour avoir du code à vous mettre sous la dent, vous trouverez BidonChienDao qui est une implémentation "bidon" de ChienDao. C'est d'ailleurs BidonChienDao qui est utilisée par Launcher.
public
class
BidonChienDao implements
ChienDao {
@Override
public
List<
Chien>
findAllChiens
(
) {
final
ArrayList<
Chien>
chiens =
new
ArrayList<
Chien>(
);
// TODO : Remplacer RaceDeChien.XXX et Sexe.XXX par des imports static.
final
SimpleChien milou =
new
SimpleChien
(
"Milou"
, RaceDeChien.CANICHE, Sexe.MALE);
final
SimpleChien lassie =
new
SimpleChien
(
"Lassie"
, RaceDeChien.BERGER_ALLEMAND, Sexe.FEMALE);
final
SimpleChien pluto =
new
SimpleChien
(
"Pluto"
, RaceDeChien.GOLDEN, Sexe.MALE);
chiens.add
(
milou);
chiens.add
(
lassie);
chiens.add
(
pluto);
return
chiens;
}
}
Comme son nom l'indique, BidonChienDao est une première version vraiment "bidon".
Pour que le code soit plus propre, cliquez sur "RaceDeChien.CANICHE" (sur la ligne correspondant à Milou) puis utilisez le raccourci [Ctrl] + [Shift] + [M] pour générer les imports static dans BidonChienDao. reproduisez l'opération sur les autres RaceDeChien.XXX et Sexe.XXX.
Pour l'instant, il n'y a que BidonChienDao qui implémente ChienDao. Dans la suite, vous allez ajouter des implémentations plus intéressantes.
Ajoutez ChienDao et BidonChienDao à votre diagramme de classe. Ce travail est à faire au stylo et à rendre à la fin de la séance.
III. Log4J▲
Durée estimée : 5 minutes.
Vous allez ajouter des "logs" dans le programme. Cela peut se faire à l'aide de "System.out.println(..)" comme c'est le cas dans Launcher mais ce n'est pas l'idéal. En effet, cette solution offre très peu d'options.
À la place, vous allez utiliser la bibliothèque Log4J. Pour cela, complétez la classe BidonChienDao pour qu'elle ressemble au code suivant. Il faut ajouter l'import de Logger, définir l'attribut LOGGER au niveau de la classe puis l'utiliser dans findAllChiens() avec le message que vous souhaitez afficher.
import
org.apache.log4j.Logger;
...
public
class
BidonChienDao implements
ChienDao {
private
static
final
Logger LOGGER =
Logger.getLogger
(
BidonChienDao.class
);
public
List<
Chien>
findAllChiens
(
) {
LOGGER.debug
(
"findAllChiens : debut"
);
...
}
Relancez Launcher. Vous devriez voir apparaître une nouvelle ligne dans la console.
TP des chiens : DEBUT
Recherche bidon
0
[main] DEBUG com.icauda.tp.chien.dao.BidonChienDao - findAllChiens : debut
Nombre de chiens : 3
Liste des chiens
SimpleChien [nom
=
Milou]
SimpleChien [nom
=
Lassie]
SimpleChien [nom
=
Pluto]
TP des chiens : FIN
Ajoutez un logueur dans Launcher puis remplacez les "System.out.println(..)" par des logs Log4J comme vous venez de le faire dans BidonChienDao. Quand vous relancerez Launcher, vous devriez avoir une trace ressemblant à la suivante :
0
[main] DEBUG com.icauda.tp.chien.Launcher - TP des chiens : DEBUT
1
[main] DEBUG com.icauda.tp.chien.Launcher - Recherche bidon
2
[main] DEBUG com.icauda.tp.chien.dao.BidonChienDao - findAllChiens : debut
5
[main] DEBUG com.icauda.tp.chien.Launcher - Nombre de chiens : 3
5
[main] DEBUG com.icauda.tp.chien.Launcher - Liste des chiens
5
[main] DEBUG com.icauda.tp.chien.Launcher - SimpleChien [nom
=
Milou]
5
[main] DEBUG com.icauda.tp.chien.Launcher - SimpleChien [nom
=
Lassie]
5
[main] DEBUG com.icauda.tp.chien.Launcher - SimpleChien [nom
=
Pluto]
6
[main] DEBUG com.icauda.tp.chien.Launcher - TP des chiens : FIN
La bibliothèque Log4J est notamment intéressante, car elle permet de spécifier un format de log mais aussi de dire si on doit logguer (ou non), à l'aide d'un fichier de configuration. Le fichier de configuration de Log4J se nomme "log4j.properties". Par exemple, on va logguer une grosse quantité d'informations durant le développement alors qu'on va limiter les logs sur les serveurs de production.
Déplacez le fichier "log4j.properties", qui se trouve dans "src/test/resources", vers "src/main/resources".
# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger
=
DEBUG, A1
# A1 is set to be a ConsoleAppender.
log4j.appender.A1
=
org.apache.log4j.ConsoleAppender
# A1 uses PatternLayout.
log4j.appender.A1.layout
=
org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern
=
%
-4r [%t
] %
-5p %c
%x
- %m%n
Cherchez, dans la documentation de Log4J, comment configurer pour que les logs indiquent également les numéros des lignes.
Log4J propose plusieurs niveaux de log : fatal, error, warn, info et debug.
Changez le niveau de log, de DEBUG à INFO, dans le fichier de configuration pour que cela ressemble au code suivant :
...
log4j.rootLogger
=
INFO, A1
...
Quand vous relancez Launcher, vous ne voyez plus aucune ligne dans la console.
Modifiez BidonChienDao pour qu'elle ressemble au code suivant. Ici, l'idée est de changer LOGGER.debug(..) en LOGGER.info(..) :
public
class
BidonChienDao implements
ChienDao {
...
public
List<
Chien>
findAllChiens
(
) {
LOGGER.debug
(
"findAllChiens : debut"
);
LOGGER.debug
(
"Une info utile en dev."
);
LOGGER.info
(
"Une info importante pour la prod."
);
...
Quand vous relancez Launcher, vous ne devriez voir que la ligne qui utilise LOGGER.info(..) :
0
[main] INFO com.icauda.tp.chien.dao.BidonChienDao - Une info importante pour la prod.
Dans la suite, vous allez surtout développer. Remettez donc le niveau de log à "DEBUG" dans "log4j.properties".
0
[main] DEBUG com.icauda.tp.chien.Launcher - TP des chiens : DEBUT
1
[main] DEBUG com.icauda.tp.chien.Launcher - Recherche bidon
2
[main] DEBUG com.icauda.tp.chien.dao.BidonChienDao - findAllChiens : debut
2
[main] DEBUG com.icauda.tp.chien.dao.BidonChienDao - Une info utile en dev.
2
[main] INFO com.icauda.tp.chien.dao.BidonChienDao - Une info importante pour la prod.
4
[main] DEBUG com.icauda.tp.chien.Launcher - Nombre de chiens : 3
4
[main] DEBUG com.icauda.tp.chien.Launcher - Liste des chiens
4
[main] DEBUG com.icauda.tp.chien.Launcher - SimpleChien [nom
=
Milou]
4
[main] DEBUG com.icauda.tp.chien.Launcher - SimpleChien [nom
=
Lassie]
4
[main] DEBUG com.icauda.tp.chien.Launcher - SimpleChien [nom
=
Pluto]
5
[main] DEBUG com.icauda.tp.chien.Launcher - TP des chiens : FIN
Les niveaux de logs de Log4J fonctionnent comme des poupées russes. Chaque niveau englobe le(s) niveau(x) inférieur(s). Cherchez la hiérarchie des niveaux prédéfinis dans la documentation et représentez la graphiquement. Ce travail est à faire au stylo et à rendre à la fin de la séance.
Il est également possible de configurer les logs à l'aide de fichiers XML, et donc pas seulement à l'aide de fichiers plats.
IV. CSV▲
Dans cette partie, vous allez utiliser des fichiers CSV. C'est un format d'échange simple et encore très utilisé dans l'industrie.
IV-A. Découverte du format▲
Durée estimée : 1 minute.
Les fichiers CSV (Comma Separated Values) sont des fichiers texte qui contiennent des données réparties par ligne. Les données de chaque ligne sont séparées par des virgules. L'ensemble forme ainsi un tableau de valeurs en deux dimensions.
Certains pays utilisent d'autres caractères que la virgule comme séparateur. En France on utilise plus volontiers le point-virgule, le trait ou même une tabulation. On préfère en effet réserver la virgule pour les nombres décimaux.
Dans un fichier CSV, les lignes commençant par le caractère dièse sont des commentaires. Les lignes vides ne comptent pas. La première ligne qui n'est ni un commentaire ni vide correspond aux entêtes des colonnes.
Le projet contient plusieurs fichiers CSV, dont "chiens-01.csv" qui liste des chiens et leurs attributs.
# Chiens
Nom;Nom complet;sexe;race;couleurs;poids
Milou;Milou de Belgique;1;caniche;blanc;12,5
Pluto;Pluto le chien Disney;1;golden_ret;jaune;24,0
Lassie;Lassie des alpes;2;berger_all;blanc,noir;32,3
Volt;Volt le super chien;1;caniche;blanc,noir;14,0
Medor;Medor;1;rottweiler;gris;32,0
Dans Eclipse, pour ouvrir un fichier CSV, il faut faire un clic droit sur le nom du fichier puis choisir le menu "Open with/Text editor". Ne double-cliquez pas sur le fichier, sinon ça va l'ouvrir dans Excel.
Ici, on remarque que le séparateur utilisé est le point-virgule, bien que rien ne l'indique dans le fichier. Il y a six colonnes dont les entêtes sont "Nom", "Nom complet", "sexe", "race", "couleurs", et "poids". Enfin il y a cinq lignes de données, correspondant ici aux attributs de cinq chiens : "Milou", "Pluto", "Lassie", "Volt" et "Medor".
Pour mieux voir la structure du fichier, vous pouvez ajouter (dans une copie du fichier) des espaces. Le fichier modifié ressemblera alors au suivant.
# Chiens
Nom; Nom complet; sexe; race; couleurs; poids
Milou; Milou de Belgique; 1; caniche; blanc; 12
,5
Pluto; Pluto le chien Disney; 1; golden_ret; jaune; 24
,0
Lassie; Lassie des alpes; 2; berger_all; blanc,noir; 32
,3
Volt; Volt le super chien; 1; caniche; blanc,noir; 14
,0
Medor; Medor; 1; rottweiler; gris; 32
,0
IV-B. Tests génériques avec JUnit▲
Durée estimée : 15 minutes.
Avant même de coder le DAO qui lira le fichier CSV, vous allez écrire des tests permettant de valider que le programme fonctionne. Cette manière de faire s'inscrit dans une démarche de qualité.
Dans les annexes et sur le Web, vous trouverez une liste d'articles qui abordent ce principe, sous les noms TDD (Test Driven Developpement) ou 3T (Tests en Trois Temps).
Pour commencer, créez un package nommé "com.icauda.tp.chien.dao.csv" dans "src/test/java". Créez ensuite une classe nommée "AbstractCsvChienDaoTest" dans ce package en cochant l'option "abstract" dans la popop. Ajoutez ensuite un peu de doc dans cette classe. Pour finir, déclarez un logger comme expliqué plus haut.
package
com.icauda.tp.chien.dao.csv;
/**
* Classe de test generique pour les DAO de chien en CSV.
*
*
@author
Thierry Leriche-Dessirier
*
*/
public
abstract
class
AbstractCsvChienDaoTest {
private
static
final
Logger LOGGER =
Logger.getLogger
(
AbstractCsvChienDaoTest.class
);
}
Si vous avez oublié de cocher la case "abstract", vous pouvez compléter le code directement.
Ajoutez ensuite deux constantes pour indiquer le fichier CSV que vous allez utiliser.
public
abstract
class
AbstractCsvChienDaoTest {
private
static
final
Logger LOGGER =
Logger.getLogger
(
AbstractCsvChienDaoTest.class
);
private
final
static
String RESOURCES_PATH =
"src/test/resources/"
;
private
final
static
String CHIENS_FILE_NAME =
"chiens-01.csv"
;
}
Comme vous voulez tester un DAO, il faut ajouter une variable :
import
com.icauda.tp.chien.dao.ChienDao;
...
public
abstract
class
AbstractCsvChienDaoTest {
...
protected
CsvChienDao dao;
}
Ici, le DAO est ajouté en "protected" car vous allez faire de l'héritage dans la suite.
Vous allez maintenant préparer l'exécution de vos tests. Pour cela, vous allez créer la méthode "doBefore()", annotée "@Before", qui va alimenter votre fichier.
import
java.io.File;
import
org.junit.Before;
...
public
abstract
class
AbstractCsvChienDaoTest {
protected
CsvChienDao dao;
...
@Before
public
void
doBefore
(
) {
LOGGER.debug
(
"doBefore Debut"
);
final
File file =
new
File
(
RESOURCES_PATH +
CHIENS_FILE_NAME);
dao.init
(
file);
LOGGER.debug
(
"doBefore Fin"
);
}
}
Les méthodes annotées avec "@Before" sont lancées avant chaque test.
Vous pouvez maintenant ajouter des tests, correspondant à votre jeu de tests (ex. chiens-01.csv). Comprenez bien que les tests seront spécifiques à ce jeu de tests. Chaque test ne doit tester qu'une seule chose (ou qu'une seule série de choses). Les méthodes de test doivent être annotées avec "@Test" :
import
org.junit.Assert;
import
org.junit.Before;
import
org.junit.Test;
...
public
abstract
class
AbstractCsvChienDaoTest {
...
protected
CsvChienDao dao;
...
/**
* Teste le nombre de chiens trouves.
*
* RESULT Nombre chiens : 5
*/
@Test
public
void
testCinqChiens
(
) {
LOGGER.debug
(
"testCinqChiens... Debut"
);
// Arrange
final
int
nombreChiensAttendus =
5
;
// Act
final
List<
Chien>
chiens =
dao.findAllChiens
(
);
// Assert
Assert.assertEquals
(
nombreChiensAttendus, chiens.size
(
));
LOGGER.debug
(
"testCinqChiens... Fin"
);
}
/**
* Teste que le premier chien est bien Milou.
*
* PARAM position : 0
* RESULT nom : Milou
*/
@Test
public
void
testPremierEstMilou
(
) {
LOGGER.debug
(
"testFirstIsMilou... Debut"
);
// Arrange
final
int
position =
0
;
final
String nomAttendu =
"Milou"
;
// Act
final
List<
Chien>
chiens =
dao.findAllChiens
(
);
final
Chien chien =
chiens.get
(
position);
// Assert
Assert.assertEquals
(
nomAttendu, chien.getNom
(
));
LOGGER.debug
(
"testFirstIsMilou... Fin"
);
}
}
Au passage, vous pouvez constater que c'est le formalisme AAA (Arrange Act Assert) qui a été choisi pour bien présenter/organiser le code des tests.
La méthode "get(..)" d'une List renvoie l'élément qui se trouve à la position indiquée en paramètre. En Java, les positions commencent à zéro.
Ajoutez des tests intéressants. Essayez de tester le plus de choses (différentes) possible. Par exemple, vous pouvez tester l'ordre des chiens, le poids de Lassie, les couleurs de la fourrure de Volt, la race de Pluto, etc. Jouez le jeu en ne regardant pas tout de suite les propositions de réponse en annexe. De manière générale, plus votre code sera testé et plus il sera légitime de penser qu'il sera fiable. N'oubliez pas de documenter votre code. Ce travail n'est pas à rendre par écrit. Appelez le professeur pour qu'il vous valide cette étape.
JUnit propose de nombreuses méthodes pour compléter "assertEquals(..)" :
- assertTrue(..) et assertFalse(..) vérifient qu'une condition est vraie ou fausse ;
- assertNull(..) et assertNotNull(..) vérifient qu'une variable est nulle ou non ;
- assertSame(..) et assertNotSame(..) vérifient des équivalences ;
- assertArrayEquals(..) teste les contenus de tableaux ;
- assertThat(..) dont la prise en main est délicate ;
- etc.
Ajoutez la classe "NaiveCsvChienDaoTest" étendant "AbstractCsvChienDaoTest" dans le package de test "com.icauda.tp.chien.dao.csv". C'est dans le constructeur de "NaiveCsvChienDaoTest" que vous allez spécifier votre DAO cible.
package
com.icauda.tp.chien.dao.csv;
/**
* Classe de test de NaiveCsvChienDao.
*
*
@author
Thierry Leriche-Dessirier
*
*/
public
class
NaiveCsvChienDaoTest extends
AbstractCsvChienDaoTest {
public
NaiveCsvChienDaoTest
(
) {
dao =
new
NaiveCsvChienDao
(
);
}
}
Pour finir, lancez l'exécution des tests de NaiveCsvChienDaoTest. Pour cela, sur le nom de la classe, à l'aide d'un clic droit, lancez le menu "Run as/JUnit test".
Vous constatez que ça lance bien tous vos tests et qu'ils sont tous en rouge pour signaler un échec. À ce stade, c'est normal ; c'est justement ce que l'on veut.
Utilisez le raccourci [Ctrl] + [Shift] + M sur "Assert.assertEquals" pour ajouter l'import statique de "assertEquals" et alléger votre code. Profitez-en pour formater le code à l'aide de [Ctrl] + [Shift] + F si ce n'est pas déjà fait.
En vous inspirant de ce que vous venez de coder (AbstractCsvChienDaoTest, NaiveCsvChienDaoTest, etc.) pour tester la (future) lecture de "chiens-01.csv", codez de nouvelles classes pour tester le jeu de tests "chiens-02.csv". N'oubliez pas de compléter "RaceDeChien" avec les valeurs manquantes. Ce travail n'est pas à rendre par écrit. Appelez le professeur pour qu'il vous valide cette étape.
IV-C. Lecture naïve▲
Durée estimée : 15 minutes.
Dans un premier temps, vous allez coder une lecture du fichier CSV sans chercher à optimiser le code. Vous allez surtout bien séparer les différentes étapes, car il y a beaucoup de choses à comprendre.
Pour lire naïvement un fichier CSV, il a principalement trois étapes :
- charger les lignes du fichier en les stockant dans une liste ou un tableau ;
- décoder chaque ligne ;
- transformer les champs des lignes en objet Chien.
Cela se traduit par le code suivant, encore très incomplet comme vous pouvez le constater :
import
java.util.ArrayList;
...
public
class
NaiveCsvChienDao implements
CsvChienDao {
...
private
List<
String>
getLignesFromFile
(
) throws
Exception {
throw
new
UnsupportedOperationException
(
"Cette methode n'est pas encore programmee."
);
}
private
Chien transformLigneToChien
(
final
String ligne) throws
Exception {
throw
new
UnsupportedOperationException
(
"Cette methode n'est pas encore programmee."
);
}
@Override
public
List<
Chien>
findAllChiens
(
) {
LOGGER.debug
(
"Chargement des chiens."
);
try
{
final
List<
String>
lignes =
getLignesFromFile
(
);
final
List<
Chien>
chiens =
new
ArrayList<
Chien>(
);
for
(
String ligne : lignes) {
final
Chien chien =
transformLigneToChien
(
ligne);
chiens.add
(
chien);
}
return
chiens;
}
catch
(
Exception e) {
LOGGER.error
(
"Une erreur s'est produite..."
, e);
return
nul;
}
}
La méthode "findAllChiens()" vous permet simplement de voir l'algorithme général, dans ses grandes lignes, sans jeu de mots…
Relancez vos tests. Vous constatez que ce n'est plus "findAllChiens()" qui les fait échouer, mais "getLignesFromFile()". Vous allez maintenant vous occuper de "getLignesFromFile()" et de "transformLigneToChien(..)" en même temps et voir comment les résultats de vos tests évoluent au fur et à mesure.
public
class
NaiveCsvChienDao implements
CsvChienDao {
...
private
List<
String>
getLignesFromFile
(
) throws
Exception {
LOGGER.debug
(
"getLignesFromFile"
);
final
List<
String>
lignes =
new
ArrayList<
String>(
);
// TODO ...
return
lignes;
}
private
Chien transformLigneToChien
(
final
String ligne) throws
Exception {
final
SimpleChien chien =
new
SimpleChien
(
);
// TODO...
return
chien;
}
Relancez les tests. Ils échouent toujours, mais plus pour la même raison. Cette fois ça dit "expected:5 but was:0", ce qui est relativement cohérent avec le code.
java.lang.AssertionError: expected:5
but was:0
at org.junit.Assert.fail
(
Assert.java:91
)
at org.junit.Assert.failNotEquals
(
Assert.java:645
)
at org.junit.Assert.assertEquals
(
Assert.java:126
)
at org.junit.Assert.assertEquals
(
Assert.java:470
)
at org.junit.Assert.assertEquals
(
Assert.java:454
)
at com.icauda.tp.chien.dao.csv.AbstractCsvChienDaoTest.testCinqChiens
(
AbstractCsvChienDaoTest.java:54
)
...
Les numéros de ligne indiqués dans la trace peuvent varier en fonction du formatage utilisé dans le code.
Il existe plusieurs manières de lire un fichier texte. La plus courante est d'utiliser une combinaison des classes "FileReader" et "BufferedReader". La première vous permet de lire le contenu d'un fichier (sous la forme de flux) et la seconde vous permet de manipuler des lignes.
private
List<
String>
getLignesFromFile
(
) throws
Exception {
LOGGER.debug
(
"getLignesFromFile"
);
final
List<
String>
lignes =
new
ArrayList<
String>(
);
final
FileReader fr =
new
FileReader
(
file);
final
BufferedReader br =
new
BufferedReader
(
fr);
// TODO ...
return
lignes;
}
Ce qu'il ne faut pas oublier, c'est de fermer ces "ressources" quand vous n'en avez plus besoin. Évidemment, il faut les fermer dans l'ordre inverse de leur ouverture…
private
List<
String>
getLignesFromFile
(
) throws
Exception {
...
final
FileReader fr =
new
FileReader
(
file);
final
BufferedReader br =
new
BufferedReader
(
fr);
// TODO ...
br.close
(
);
fr.close
(
);
}
Il ne reste plus qu'à parcourir le fichier :
private
List<
String>
getLignesFromFile
(
) throws
Exception {
...
for
(
String ligne =
br.readLine
(
); ligne !=
null
; ligne =
br.readLine
(
)) {
lignes.add
(
ligne);
}
...
}
N'ayez pas peur. C'est une simple boucle "for" en trois parties : initialisation, condition d'arrêt et incrémentation.
Il faut maintenant filtrer les lignes vides ou de commentaire :
private
List<
String>
getLignesFromFile
(
) throws
Exception {
...
for
(
String ligne =
br.readLine
(
); ligne !=
null
; ligne =
br.readLine
(
)) {
// Suppression des espaces en trop
ligne =
ligne.trim
(
);
// Filtre des lignes vides
if
(
ligne.isEmpty
(
)) {
continue
;
}
// Filtre des lignes de commentaire
if
(
ligne.startsWith
(
"#"
)) {
continue
;
}
lignes.add
(
ligne);
}
...
}
Relancez les tests. Vous constatez que "testCinqChiens" échoue, car il attend cinq chiens, mais en trouve six. C'est parce que vous n'avez pas encore filtré la première ligne, correspondant aux titres.
public
List<
Chien>
findAllChiens
(
) {
...
try
{
final
List<
String>
lignes =
getLignesFromFile
(
);
final
String ligneEntete =
lignes.remove
(
0
);
LOGGER.debug
(
"Entetes : "
+
ligneEntete);
...
Relancez encore les tests. Cette fois "testCinqChiens" devrait passer au vert, ce qui signifie qu'on en a "fini" avec la méthode "getLignesFromFile()".
Le plus dur est fait. Il ne reste plus qu'à décoder les lignes. Pour commencer, il faut séparer les colonnes.
private
Chien transformLigneToChien
(
final
String ligne) throws
Exception {
final
SimpleChien chien =
new
SimpleChien
(
);
final
String separator =
";"
;
final
String[] values =
ligne.split
(
separator);
// TODO...
return
chien;
}
Regardez la doc Java de la méthode "split(..)" pour bien comprendre comment elle fonctionne.
Vous pouvez maintenant commencer à remplir l'objet Chien.
private
Chien transformLigneToChien
(
final
String ligne) throws
Exception {
final
SimpleChien chien =
new
SimpleChien
(
);
final
String separator =
";"
;
final
String[] values =
ligne.split
(
separator);
chien.setNom
(
values[0
]);
// TODO...
return
chien;
}
Relancez les tests. À ce stade, le test "testPremierEstMilou" devrait lui aussi passer au vert. On approche de la solution.
La suite du document suppose que vous ayez ajouté d'autres tests. À titre d'illustration (cf. annexes), j'ai ajouté "testPoidsLassie", "testCouleursVolt" et "testRacePluto".
Vous pouvez passer aux autres attributs :
private
Chien transformLigneToChien
(
final
String ligne) throws
Exception {
final
SimpleChien chien =
new
SimpleChien
(
);
final
String separator =
";"
;
final
String[] values =
ligne.split
(
separator);
chien.setNom
(
values[0
]);
chien.setNomComplet
(
values[1
]);
final
String tempSexe =
values[2
];
final
Sexe sexe =
Sexe.valueOfByCode
(
new
Integer
(
tempSexe));
chien.setSexe
(
sexe);
final
String tempRace =
values[3
];
final
RaceDeChien race =
RaceDeChien.valueOfByCode
(
tempRace);
chien.setRace
(
race);
// TODO...
return
chien;
}
Quand vous relancez les tests, "testRacePluto" devrait passer au vert.
Complétez la méthode de transformation pour les attributs couleurs et poids. Pour les couleurs, vous pouvez à nouveau faire appel à "split(..)". Pour le poids, n'oubliez pas que les nombres utilisent des points et non de virgules en Java.
À ce stade, tous vos tests (y compris ceux que vous avez inventés vous-même) devraient passer au vert. Vous êtes donc légitimement en droit de penser que le travail est fini.
Prenez quelques instants pour analyser comment les logs s'enchaînent dans la console. Vérifiez surtout que les traces de "doBefore()" apparaissent bien avant chaque test.
Vous allez maintenant utiliser ce nouveau DAO dans votre Launcher. Pour cela, inspirez-vous du code suivant :
public
class
Launcher {
public
static
void
main
(
String[] args) {
LOGGER.debug
(
"TP des chiens : DEBUT"
);
doFindChiensBidon
(
);
doFindChiensNaif
(
);
...
private
static
void
doFindChiensNaif
(
) {
LOGGER.debug
(
"Recherche naïve"
);
final
String fileName =
"src/main/resources/chiens.csv"
;
final
File file =
new
File
(
fileName);
final
CsvChienDao dao =
new
NaiveCsvChienDao
(
);
dao.init
(
file);
final
List<
Chien>
chiens =
dao.findAllChiens
(
);
final
int
nombreDeChiens =
chiens.size
(
);
LOGGER.debug
(
"Nombre de chiens : "
+
nombreDeChiens);
printChiens
(
chiens);
}
...
Ajoutez quelques lignes, à votre convenance, dans "src/main/resources/chiens.csv". Vous pouvez copier les lignes de "chiens-01.csv" si vous n'avez pas d'idée.
Vous pouvez relancer Launcher et vérifier les logs produits.
Faites un diagramme de séquence UML, ilustrant le fonctionnement de la méthode "findAllChiens" en incluant les appels aux autres classes/méthodes utilisées (hors getter/setter). Ce travail est à faire au stylo et à rendre à la fin de la séance.
IV-D. Version améliorée▲
Durée estimée : 30 minutes.
La version naïve du DAO peut très clairement être améliorée. Ce qui ne va pas changer (ou presque, cf. entêtes), ce sont les tests.
Créez la classe AdvancedCsvChienDao et la classe de test AdvanceCsvChienDaoTest, ressemblant aux classes déjà écrites, en vous inspirant du code suivant :
public
class
AdvancedCsvChienDao implements
CsvChienDao {
private
static
final
Logger LOGGER =
Logger.getLogger
(
AdvancedCsvChienDao.class
);
private
File file;
@Override
public
void
init
(
File file) {
LOGGER.debug
(
"init"
);
this
.file =
file;
}
@Override
public
List<
Chien>
findAllChiens
(
) {
LOGGER.debug
(
"findAllChiens"
);
throw
new
UnsupportedOperationException
(
"pas encore fait"
);
}
@Override
public
File getFile
(
) {
return
file;
}
}
public
class
AdvanceCsvChienDaoTest extends
AbstractCsvChienDaoTest {
private
static
final
Logger LOGGER =
Logger.getLogger
(
AdvanceCsvChienDaoTest.class
);
public
AdvanceCsvChienDaoTest
(
) {
LOGGER.debug
(
"Constructeur..."
);
dao =
new
AdvancedCsvChienDao
(
);
}
}
Lancez la nouvelle classe de test. Sans surprise, tout est rouge. Vous allez maintenant refaire le même travail qu'un peu plus tôt, mais de manière légèrement différente.
D'abord, faites en sorte que la lecture du fichier ne soit réalisée qu'à l'initialisation (appel de "init") et non à chaque fois qu'on appelle "findAllChiens()" comme c'était le cas dans la version naïve, en vous inspirant du code suivant :
public
class
AdvancedCsvChienDao implements
CsvChienDao {
private
static
final
Logger LOGGER =
Logger.getLogger
(
AdvancedCsvChienDao.class
);
private
File file;
private
List<
Chien>
chiens;
@Override
public
void
init
(
File file) {
LOGGER.debug
(
"init"
);
this
.file =
file;
// On relance une lecture a chaque initialisation, ce qui permet de
// changer de fichier, ou de recharger ledit fichier.
reloadChiens
(
);
}
/**
* Chargement des chiens.
*/
private
void
reloadChiens
(
) {
LOGGER.debug
(
"findAllChiens"
);
if
(
file ==
null
) {
throw
new
IllegalStateException
(
"Le fichier est nul..."
);
}
throw
new
UnsupportedOperationException
(
"Bientot..."
);
}
@Override
public
List<
Chien>
findAllChiens
(
) {
LOGGER.debug
(
"findAllChiens"
);
if
(
chiens ==
null
) {
throw
new
IllegalStateException
(
"La liste n'a pas encore ete initialisee..."
);
}
return
chiens;
}
...
Pour la suite, il suffit de reprendre ce que vous aviez déjà fait dans la version naïve… Commencez par ajouter, avec un simple copier-coller, les méthodes "getLignesFromFile()" et "transformLigneToChien(..)" puis modifiez "reloadChiens()" en vous inspirant du code suivant :
private
void
reloadChiens
(
) {
LOGGER.debug
(
"findAllChiens"
);
if
(
file ==
null
) {
throw
new
IllegalStateException
(
"Le fichier est nul..."
);
}
try
{
final
List<
String>
lignes =
getLignesFromFile
(
);
final
String ligneEntete =
lignes.remove
(
0
);
LOGGER.debug
(
"Entetes : "
+
ligneEntete);
chiens =
new
ArrayList<
Chien>(
lignes.size
(
));
for
(
String ligne : lignes) {
final
Chien chien =
transformLigneToChien
(
ligne);
chiens.add
(
chien);
}
}
catch
(
Exception e) {
LOGGER.error
(
"Une erreur s'est produite..."
, e);
}
}
Notez qu'on initialise directement la liste des chiens avec le bon nombre d'items :
chiens =
new
ArrayList<
Chien>(
lignes.size
(
));
Relancez les tests. Tout doit être vert. Par acquit de conscience, relancez également les tests de NaiveCsvChienDaoTest. Ils devraient rester verts, sauf si vous avez fait une mauvaise manipulation lors du copier-coller.
Pour la suite, vous allez coder une meilleure gestion des ressources. En effet, que se passe-t-il (dans la version naïve) lorsqu'une exception survient durant la boucle "for" ? Ça sort tout simplement de la méthode en lançant une exception et vos appels aux méthodes "close()" ne sont pas réalisés… Vous devez obligatoirement permettre ces appels en vous inspirant du code suivant :
private
List<
String>
getLignesFromFile
(
) {
LOGGER.debug
(
"getLignesFromFile"
);
final
List<
String>
lignes =
new
ArrayList<
String>(
);
FileReader fr =
null
;
BufferedReader br =
null
;
try
{
fr =
new
FileReader
(
file);
br =
new
BufferedReader
(
fr);
for
(
String ligne =
br.readLine
(
); ligne !=
null
; ligne =
br.readLine
(
)) {
// Suppression des espaces en trop
ligne =
ligne.trim
(
);
// Filtre des lignes vides
if
(
ligne.isEmpty
(
)) {
continue
;
}
// Filtre des lignes de commentaire
if
(
ligne.startsWith
(
"#"
)) {
continue
;
}
lignes.add
(
ligne);
}
}
catch
(
IOException e) {
LOGGER.error
(
"Lecture impossible"
, e);
}
finally
{
if
(
br !=
null
) {
try
{
br.close
(
);
}
catch
(
IOException e) {
LOGGER.error
(
"Fermeture impossible"
, e);
}
}
if
(
fr !=
null
) {
try
{
fr.close
(
);
}
catch
(
IOException e) {
LOGGER.error
(
"Fermeture impossible"
, e);
}
}
}
return
lignes;
}
Vous devez connaître ce code par cœur. En Java 7, il existe les "try-with-resources" ("try améliorés"), mais encore peu d'entreprises utilisent Java 7 pour l'instant.
IV-D-1. Entêtes▲
Dans la version naïve, on sautait la ligne des entêtes et on perdait de l'information. Vous allez maintenant rectifier cela en vous inspirant du code suivant :
public
class
AdvancedCsvChienDao implements
CsvChienDao {
private
final
static
String SEPARATOR =
";"
;
private
List<
String>
entetes;
...
private
void
transformEntetes
(
final
String ligneEntete) {
LOGGER.debug
(
"transformEntetes"
);
final
String[] tabEntetes =
ligneEntete.split
(
SEPARATOR);
entetes =
new
ArrayList<
String>(
tabEntetes.length);
for
(
String entete : tabEntetes) {
entetes.add
(
entete);
}
}
private
void
reloadChiens
(
) {
...
try
{
final
List<
String>
lignes =
getLignesFromFile
(
);
final
String ligneEntete =
lignes.remove
(
0
);
LOGGER.debug
(
"Entetes : "
+
ligneEntete);
transformEntetes
(
ligneEntete);
chiens =
new
ArrayList<
Chien>(
lignes.size
(
));
...
public
List<
String>
getEntetes
(
) {
return
entetes;
}
...
Relancez les tests pour vérifier que tout est toujours bon.
Au point où vous en êtes, vous vous dites que les entêtes sont importants et que vous allez carrément les imposer à tous les DAO en le spécifiant dans l'interface :
public
interface
CsvChienDao extends
ChienDao {
public
void
init
(
File file);
public
File getFile
(
);
public
List<
String>
getEntetes
(
);
}
Du coup, vous pouvez maintenant annoter "getEntetes()" avec "@Override" :
@Override
public
List<
String>
getEntetes
(
) {
return
entetes;
}
Et surtout, vous devez compléter NaiveCsvChienDao pour que ça continue de compiler. Comme vous ne souhaitez pas investir de temps supplémentaire sur cette (mauvaise) implémentation, vous allez simplement renvoyer une exception.
public
class
NaiveCsvChienDao implements
CsvChienDao {
...
@Override
public
List<
String>
getEntetes
(
) {
throw
new
UnsupportedOperationException
(
"Fonction non disponible, et ne le sera jamais..."
);
}
Relancez tous les tests pour vérifier que ça fonctionne toujours.
Sans surprise, vous allez maintenant ajouter des tests pour valider cette nouvelle fonctionnalité. Notez que vous auriez même dû le faire avant d'écrire la fonctionnalité. Mais disons que vous n'y aviez pas pensé (n'est-ce pas ?) dans le feu de l'action…
public
class
AbstractCsvChienDaoTest implements
CsvChienDao {
...
@Test
public
void
testTailleEntetes
(
) {
LOGGER.debug
(
"testTailleEntetes... Debut"
);
// Arrange
final
int
tailleAttendue =
6
;
// Act
final
List<
String>
entetes =
dao.getEntetes
(
);
// Assert
Assert.assertEquals
(
tailleAttendue, entetes.size
(
));
LOGGER.debug
(
"testTailleEntetes... Fin"
);
}
Relancez tous les tests. Vous constatez que les tests de AdvanceCsvChienDaoTest passent au vert, mais qu'il y a un échec sur NaiveCsvChienDaoTest. C'est normal puisque NaiveCsvChienDaoTest renvoie une exception directement. Ici, puisque vous ne souhaitez plus investir de temps sur la version naïve, vous avez deux solutions : soit vous supprimez purement et simplement cette version, soit vous gérez un cas particulier dans les tests. Pour vous montrer la gestion des exceptions attendues dans les tests, c'est la seconde solution que vous allez coder en surchargeant la méthode "testTailleEntetes()" :
public
class
NaiveCsvChienDaoTest extends
AbstractCsvChienDaoTest {
...
@Test
(
expected =
UnsupportedOperationException.class
)
@Override
public
void
testTailleEntetes
(
) {
LOGGER.debug
(
"testTailleEntetes... Debut"
);
// Arrange
// Act
final
List<
String>
entetes =
dao.getEntetes
(
);
// Assert
LOGGER.debug
(
"testTailleEntetes... Fin"
);
}
Relancez tous les tests pour vérifier que tout remarche.
Ajoutez d'autres tests des entêtes, par exemple pour en vérifier l'ordre.
Faites un diagramme de séquence UML, incluant les quatre objets du domaine. Ce travail est à faire au stylo et à rendre à la fin de la séance.
IV-D-2. Recherche▲
Vous n'allez tout de même pas vous arrêter en si bon chemin ? Maintenant que vous savez charger la liste des chiens, vous avez (très) envie de faire des recherches dans cette liste, par exemple en cherchant "Lassie".
Modifiez donc l'interface ChienDao en vous inspirant du code suivant :
public
interface
ChienDao {
...
Chien findChienByNom
(
final
String nom);
Complétez BidonChienDao, NaiveCsvChienDao et AdvancedCsvChienDao pour que ces classes compilent. Dans la suite, on ne s'intéressera qu'à AdvancedCsvChienDao…
...
@Override
public
Chien findChienByNom
(
String nom) {
throw
new
UnsupportedOperationException
(
"Bientot dispo"
);
}
Cette fois, vous n'allez pas vous laisser emporter par la musique. Vous allez écrire les tests avant de coder cette fonction de recherche :
public
abstract
class
AbstractCsvChienDaoTest {
/**
* Teste la recherche d'un chien.
*
* PARAM nom : Lassie
<
br/
>
* RESULT poids : 32.3
*/
@Test
public
void
testRechercheLassie
(
) {
LOGGER.debug
(
"testRechercheLassie... Debut"
);
// Arrange
final
String nom =
"Lassie"
;
final
Double poidsAttendu =
32.3
;
// Act
final
Chien chien =
dao.findChienByNom
(
nom);
// Assert
Assert.assertNotNull
(
chien);
Assert.assertEquals
(
nom, chien.getNom
(
));
Assert.assertEquals
(
poidsAttendu, chien.getPoids
(
));
LOGGER.debug
(
"testRechercheLassie... Fin"
);
}
/**
* Teste la recherche d'un chien qui n'est pas dans la liste.
*
* PARAM nom : Idefix
<
br/
>
* RESULT null
*/
@Test
public
void
testRechercheIdefix
(
) {
LOGGER.debug
(
"testRechercheIdefix... Debut"
);
// Arrange
final
String nom =
"Idefix"
;
// Act
final
Chien chien =
dao.findChienByNom
(
nom);
// Assert
Assert.assertNull
(
chien);
LOGGER.debug
(
"testRechercheIdefix... Fin"
);
}
C'est le moment de lancer les tests. Les deux nouveaux tests doivent être en échec.
Comme vous l'avez fait précédemment, vous pouvez maintenant coder la fonctionnalité de recherche en lançant les tests au fur et à mesure. Voici une première version naïve :
public
class
AdvancedCsvChienDao implements
CsvChienDao {
...
@Override
public
Chien findChienByNom
(
final
String nom) {
if
(
nom ==
null
||
nom.isEmpty
(
)) {
throw
new
IllegalArgumentException
(
"Le nom ne peut pas etre vide."
);
}
if
(
chiens ==
null
) {
throw
new
IllegalStateException
(
"La liste n'a pas encore ete initialisee..."
);
}
for
(
Chien chien : chiens) {
if
(
nom.trim
(
).equalsIgnoreCase
(
chien.getNom
(
))) {
return
chien;
}
}
// Si pas trouve...
return
nul;
}
Le truc moche, avec cette première version, c'est qu'il faut parcourir la liste à chaque fois qu'une recherche est lancée. Ce n'est donc pas très performant. On aimerait donc bien mettre en place un système d'indexation (cache) pour améliorer les performances.
Regardez la documentation de HashMap pour comprendre comment elle fonctionne. "HashMap" est l'implémentation de "Map" la plus utilisée. Ses deux méthodes les plus importantes sont "put(..)" et "get(..)". Indexez les données à l'aide d'une Map.
Le code devrait (plusieurs solutions possibles) maintenant ressembler au suivant, avec votre solution à la place des "XXX" :
public
class
AdvancedCsvChienDao implements
CsvChienDao {
private
Map<
String, Chien>
chienMapByNom;
...
@Override
public
Chien findChienByNom
(
final
String nom) {
private
void
reloadChiens
(
) {
...
chiens =
new
ArrayList<
Chien>(
lignes.size
(
));
chienMapByNom =
new
HashMap<
String, Chien>(
lignes.size
(
));
for
(
String ligne : lignes) {
final
Chien chien =
transformLigneToChien
(
ligne);
chiens.add
(
chien);
XXX
}
...
@Override
public
Chien findChienByNom
(
final
String nom) {
if
(
nom ==
null
||
nom.isEmpty
(
)) {
throw
new
IllegalArgumentException
(
"Le nom ne peut pas etre vide."
);
}
if
(
chiens ==
null
) {
throw
new
IllegalStateException
(
"La liste n'a pas encore ete initialisee..."
);
}
return
XXX
}
...
C'est le nom du chien qui sert de clé. Que se passe-t-il si deux chiens (ou plus) ont le même nom ? La réponse à cette question est aussi à rendre à la fin de la séance. N'hésitez pas à modifier le code pour vous aider à répondre. Ce travail est à faire au stylo et à rendre à la fin de la séance.
Comme toujours, pensez à lancer les tests pour vérifier que tout est toujours vert.
Vous pourriez encore ajouter de nombreuses fonctionnalités (tris, comptages, recherches croisées, etc.), mais vous pouvez en rester là pour ce TP.
Quand vous aurez le temps, je vous invite à lire l'article Les fichiers CSV avec JavaLes fichiers CSV avec Java qui détaille les contraintes liées à la lecture d'un fichier CSV.
IV-E. OpenCsv▲
Durée estimée : 15 minutes.
Comme vous venez de le constater, c'est relativement simple de lire des fichiers CSV en Java. Toutefois, c'est sans compter avec les optimisations plus complexes, la lecture des (très) gros fichiers, avec une forte volumétrie, etc. Et puis, surtout, vous n'avez pas envie de consacrer du temps à redévelopper des fonctionnalités que plusieurs bibliothèques gratuites réalisent déjà très bien.
C'est important de l'avoir fait au moins une fois, pour savoir de quoi il s'agit, ne serait-ce que pour les points essentiels.
Vous allez maintenant utiliser la bibliothèque "Open CSV" qui s'impose comme l'une des meilleures du marché. Pour cela, vous devez modifier votre configuration Maven (ex. pom.xml) comme suit :
...
<properties>
...
<!-- Lib CSV -->
<opencsv.version>
2.3</opencsv.version>
...
</properties>
<dependencies>
...
<!-- Lib CSV -->
<dependency>
<groupId>
net.sf.opencsv</groupId>
<artifactId>
opencsv</artifactId>
<version>
${opencsv.version}</version>
</dependency>
...
</dependencies>
...
Relancez une compilation Maven à l'aide de la commande "build" comme vous l'avez fait au début de ce TP. En fonction des éléments déjà présents sur votre machine, le résultat devrait ressemble à la trace suivante.
C:\TP\tp-chien-dao>
build
C:\TP\tp-chien-dao>
echo Build du TP Chien DAO
Build du TP Chien DAO
C:\TP\tp-chien-dao>
mvn clean install eclipse:eclipse
C:\TP\tp-chien-dao>
set JAVA_HOME
=
C:\dev\javas\jdk1.6
.0_24
[INFO] Scanning for
projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building TP Chien 1
.0
-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2
.4
.1
:clean (
default-clean) @ tp-chien-dao ---
[INFO] Deleting C:\TP\tp-chien-dao\target
[INFO]
[INFO] --- maven-resources-plugin:2
.4
.3
:resources (
default-resources) @ tp-chien-dao ---
[INFO] Using 'UTF-8'
encoding to copy filtered resources.
[INFO] Copying 2
resources
[INFO]
[INFO] --- maven-compiler-plugin:2
.3
.1
:compile (
default-compile) @ tp-chien-dao ---
[INFO] Compiling 10
source files to C:\TP\tp-chien-dao\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2
.4
.3
:testResources (
default-testResources) @ tp-chien-dao ---
[INFO] Using 'UTF-8'
encoding to copy filtered resources.
[INFO] Copying 3
resources
[INFO]
[INFO] --- maven-compiler-plugin:2
.3
.1
:testCompile (
default-testCompile) @ tp-chien-dao ---
[INFO] Compiling 3
source files to C:\TP\tp-chien-dao\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2
.9
:test (
default-test) @ tp-chien-dao ---
[INFO] Surefire report directory: C:\TP\tp-chien-dao\target\surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.icauda.tp.chien.dao.csv.AdvanceCsvChienDaoTest
0
[main] DEBUG com.icauda.tp.chien.dao.csv.AdvanceCsvChienDaoTest - Constructeur...
4
[main] DEBUG com.icauda.tp.chien.dao.csv.AbstractCsvChienDaoTest - doBefore Debut
4
[main] DEBUG com.icauda.tp.chien.dao.csv.AdvancedCsvChienDao - init
4
[main] DEBUG com.icauda.tp.chien.dao.csv.AdvancedCsvChienDao - findAllChiens
4
[main] DEBUG com.icauda.tp.chien.dao.csv.AdvancedCsvChienDao - getLignesFromFile
5
[main] DEBUG com.icauda.tp.chien.dao.csv.AdvancedCsvChienDao - Entetes : Nom;Nom complet;sexe;race;couleurs;poids
5
[main] DEBUG com.icauda.tp.chien.dao.csv.AdvancedCsvChienDao - transformEntetes
9
[main] DEBUG com.icauda.tp.chien.dao.csv.AbstractCsvChienDaoTest - doBefore Fin
...
59
[main] DEBUG com.icauda.tp.chien.dao.csv.AbstractCsvChienDaoTest - testRechercheIdefix... Fin
Tests run: 9
, Failures: 0
, Errors: 0
, Skipped: 0
, Time elapsed: 0
.303
sec
Running com.icauda.tp.chien.dao.csv.NaiveCsvChienDaoTest
70
[main] DEBUG com.icauda.tp.chien.dao.csv.NaiveCsvChienDaoTest - Constructeur...
71
[main] DEBUG com.icauda.tp.chien.dao.csv.AbstractCsvChienDaoTest - doBefore Debut
...
93
[main] DEBUG com.icauda.tp.chien.dao.csv.AbstractCsvChienDaoTest - testRacePluto... Fin
Tests run: 9
, Failures: 0
, Errors: 0
, Skipped: 0
, Time elapsed: 0
.039
sec
Results :
Tests run: 18
, Failures: 0
, Errors: 0
, Skipped: 0
[INFO]
[INFO] --- maven-jar-plugin:2
.3
.1
:jar (
default-jar) @ tp-chien-dao ---
[INFO] Building jar: C:\TP\tp-chien-dao\target\TP chien.jar
[INFO]
[INFO] --- maven-install-plugin:2
.3
.1
:install (
default-install) @ tp-chien-dao ---
[INFO] Installing C:\TP\tp-chien-dao\target\TP chien.jar to C:\dev\mavens\repository\com\icauda\tp-chien-dao\1
.0
-SNAPSHOT\tp-chien-dao-1
.0
-SNAPSHOT.jar
[INFO] Installing C:\TP\tp-chien-dao\pom.xml to C:\dev\mavens\repository\com\icauda\tp-chien-dao\1
.0
-SNAPSHOT\tp-chien-dao-1
.0
-SNAPSHOT.pom
[INFO]
[INFO] maven-eclipse-plugin:2
.8
:eclipse (
default-cli) @ tp-chien-dao
[INFO]
[INFO] maven-eclipse-plugin:2
.8
:eclipse (
default-cli) @ tp-chien-dao
[INFO]
[INFO] --- maven-eclipse-plugin:2
.8
:eclipse (
default-cli) @ tp-chien-dao ---
[INFO] Using Eclipse Workspace: null
[INFO] Adding default classpath container: org.eclipse.jdt.launching.JRE_CONTAINER
[INFO] File C:\TP\tp-chien-dao\.project already exists.
Additional settings will be preserved, run mvn eclipse:clean if
you want old settings to be removed.
[INFO] Wrote Eclipse project for
"tp-chien-dao"
to C:\TP\tp-chien-dao.
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 12
.382s
[INFO] Finished at: Tue Nov 13
20
:48
:30
CET 2012
[INFO] Final Memory: 14M/61M
[INFO] ------------------------------------------------------------------------
Maven reconstruit le projet en ajoutant la nouvelle dépendance. Au passage vous remarquez que Maven a rejoué tous vos tests. Cet aspect est assez intéressant dans une démarche d'intégration continue.
Faites ensuite un "Refresh" (touche [F5] sur "tp-chien-dao") de votre projet dans Eclipse.
Avant de programmer cette nouvelle version du DAO, vous allez écrire les tests associés. Ici il va suffire de recopier AdvanceCsvChienDaoTest et de créer OpenCsvChienDao en vous inspirant des codes suivants :
public
class
OpenCsvChienDao implements
CsvChienDao {
private
static
final
Logger LOGGER =
Logger.getLogger
(
OpenCsvChienDao.class
);
private
final
static
char
SEPARATOR =
';'
;
private
File file;
@Override
public
List<
Chien>
findAllChiens
(
) {
throw
new
UnsupportedOperationException
(
"Bientot"
);
}
@Override
public
Chien findChienByNom
(
String nom) {
throw
new
UnsupportedOperationException
(
"Bientot"
);
}
@Override
public
void
init
(
File file) {
LOGGER.debug
(
"init"
);
this
.file =
file;
}
@Override
public
File getFile
(
) {
return
file;
}
@Override
public
List<
String>
getEntetes
(
) {
throw
new
UnsupportedOperationException
(
"Bientot"
);
}
}
public
class
OpenCsvChienDaoTest extends
AbstractCsvChienDaoTest {
private
static
final
Logger LOGGER =
Logger.getLogger
(
OpenCsvChienDaoTest.class
);
public
OpenCsvChienDaoTest
(
) {
LOGGER.debug
(
"Constructeur..."
);
dao =
new
OpenCsvChienDao
(
);
}
}
Dans la foulée, vous pouvez lancer les tests de OpenCsvChienDaoTest et vérifier qu'ils sont bien tous en échec.
Ajoutez des attributs de classe comme vous l'aviez fait plus tôt, en vous inspirant du code suivant :
public
class
OpenCsvChienDao implements
CsvChienDao {
...
private
File file;
private
List<
Chien>
chiens;
private
Map<
String, Chien>
chienMapByNom;
private
List<
String>
entetes;
...
@Override
public
List<
String>
getEntetes
(
) {
return
entetes;
}
Vous pouvez également reprendre quelques méthodes simples :
public
class
OpenCsvChienDao implements
CsvChienDao {
...
private
void
reloadChiens
(
) {
throw
new
UnsupportedOperationException
(
"Bientot"
);
}
@Override
public
List<
Chien>
findAllChiens
(
) {
LOGGER.debug
(
"findAllChiens"
);
if
(
chiens ==
null
) {
throw
new
IllegalStateException
(
"La liste n'a pas encore ete initialisee..."
);
}
return
chiens;
}
@Override
public
Chien findChienByNom
(
final
String nom) {
if
(
nom ==
null
||
nom.isEmpty
(
)) {
throw
new
IllegalArgumentException
(
"Le nom ne peut pas etre vide."
);
}
if
(
chiens ==
null
) {
throw
new
IllegalStateException
(
"La liste n'a pas encore ete initialisee..."
);
}
return
chienMapByNom.get
(
nom);
}
@Override
public
void
init
(
File file) {
LOGGER.debug
(
"init"
);
this
.file =
file;
reloadChiens
(
);
}
...
Vous pouvez enfin passer à la lecture à l'aide d'Open CSV. Pour cela, commencez par reprendre la méthode "getLignesFromFile()" :
public
class
OpenCsvChienDao implements
CsvChienDao {
...
private
void
reloadChiens
(
) {
LOGGER.debug
(
"reloadChiens"
);
if
(
file ==
null
) {
throw
new
IllegalStateException
(
"Le fichier est nul..."
);
}
final
List<
String[] >
lignes =
getLignesFromFile
(
);
}
private
List<
String[] >
getLignesFromFile
(
) {
LOGGER.debug
(
"getLignesFromFile"
);
if
(
file ==
null
) {
throw
new
IllegalStateException
(
"Le fichier est nul..."
);
}
throw
new
UnsupportedOperationException
(
"Bientot"
);
}
Dans cette version, la méthode "getLignesFromFile()" renvoie une liste de tableaux de Strings et non plus simplement une liste de Strings.
Le cœur d'Open CSV est la classe CSVReader :
import
java.io.FileReader;
import
au.com.bytecode.opencsv.CSVReader;
...
public
class
OpenCsvChienDao implements
CsvChienDao {
...
private
List<
String[] >
getLignesFromFile
(
) {
LOGGER.debug
(
"getLignesFromFile"
);
if
(
file ==
null
) {
throw
new
IllegalStateException
(
"Le fichier est nul..."
);
}
try
{
final
FileReader fr =
new
FileReader
(
file);
final
CSVReader csvReader =
new
CSVReader
(
fr, SEPARATOR);
}
catch
(
Exception e) {
LOGGER.error
(
"aie aie aie"
, e);
}
throw
new
UnsupportedOperationException
(
"Bientot"
);
}
Vous pouvez maintenant charger et filtrer les lignes à l'aide d'Open CSV :
public
class
OpenCsvChienDao implements
CsvChienDao {
...
private
List<
String[] >
getLignesFromFile
(
) {
LOGGER.debug
(
"getLignesFromFile"
);
if
(
file ==
null
) {
throw
new
IllegalStateException
(
"Le fichier est nul..."
);
}
final
List<
String[] >
lignes =
new
ArrayList<
String[] >(
);
try
{
final
FileReader fr =
new
FileReader
(
file);
final
CSVReader csvReader =
new
CSVReader
(
fr, SEPARATOR);
String[] nextLine =
null
;
while
((
nextLine =
csvReader.readNext
(
)) !=
null
) {
int
size =
nextLine.length;
// ligne vide
if
(
size ==
0
) {
continue
;
}
String debut =
nextLine[0
].trim
(
);
if
(
debut.isEmpty
(
) &&
size ==
1
) {
continue
;
}
// ligne de commentaire
if
(
debut.startsWith
(
"#"
)) {
continue
;
}
lignes.add
(
nextLine);
}
}
catch
(
Exception e) {
LOGGER.error
(
"aie aie aie"
, e);
}
return
lignes;
}
Pour la conversion, il suffit de reprendre ce qui avait déjà été fait. Cette fois vous pouvez directement passer un tableau.
public
class
OpenCsvChienDao implements
CsvChienDao {
...
private
Chien transformLigneToChien
(
final
String[] values) throws
Exception {
final
SimpleChien chien =
new
SimpleChien
(
);
chien.setNom
(
values[0
]);
...
Et pour finir avec la méthode de rechargement :
public
class
OpenCsvChienDao implements
CsvChienDao {
...
private
void
reloadChiens
(
) {
LOGGER.debug
(
"reloadChiens"
);
if
(
file ==
null
) {
throw
new
IllegalStateException
(
"Le fichier est nul..."
);
}
try
{
final
List<
String[] >
lignes =
getLignesFromFile
(
);
final
String[] ligneEntete =
lignes.remove
(
0
);
transformEntetes
(
ligneEntete);
chiens =
new
ArrayList<
Chien>(
lignes.size
(
));
chienMapByNom =
new
HashMap<
String, Chien>(
lignes.size
(
));
for
(
String[] ligne : lignes) {
final
Chien chien =
transformLigneToChien
(
ligne);
chiens.add
(
chien);
chienMapByNom.put
(
chien.getNom
(
), chien);
}
}
catch
(
Exception e) {
LOGGER.error
(
"Une erreur s'est produite..."
, e);
}
}
...
N'oubliez pas de programmer la méthode "transformEntetes(..)" en faisant les évolutions qui s'imposent.
Relancez les tests, qui devraient donc être tous verts.
Comme vous le constatez, vous n'avez pas gagné tant que ça au niveau du nombre de lignes et/ou de la quantité de travail à fournir. L'intérêt d'Open CSV réside surtout dans le fait que vous n'avez pas eu à décoder vous-même les lignes. Par exemple, vous n'avez pas traité les valeurs sur plusieurs lignes ou les caractères interdits ; Open CSV l'a fait pour vous de manière transparente.
Quand vous aurez le temps, je vous invite à lire l'article Charger des données depuis un fichier CSV simple en 5 minutesCharger des données depuis un fichier CSV simple en 5 minutes qui propose une autre explication/démo de la lecture d'un fichier CSV à l'aide d'Open CSV.
Dessinez un diagramme de séquence UML, correspondant au mécanisme de chargement de OpenCsvChienDao. Ce travail est à faire au stylo et à rendre à la fin de la séance.
IV-F. CSV Engine (avec des annotations)▲
Durée estimée : 10 minutes.
Vous allez maintenant utiliser la bibliothèque "CSV Engine", qui s'appuie sur Open CSV et qui va radicalement simplifier votre travail.
Commencez en ajoutant une nouvelle dépendance dans le fichier "pom.xml" :
...
<properties>
...
<!-- Lib CSV -->
<!--<opencsv.version>2.3</opencsv.version>-->
<csvengine.version>
1.3.5</csvengine.version>
...
</properties>
<dependencies>
...
<!-- Lib CSV -->
<!--
<dependency>
<groupId>net.sf.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>${opencsv.version}</version>
</dependency>
-->
<dependency>
<groupId>
fr.ybonnel</groupId>
<artifactId>
csvengine</artifactId>
<version>
${csvengine.version}</version>
</dependency>
...
</dependencies>
<repositories>
<repository>
<id>
ybonnel-release</id>
<url>
https://repository-ybonnel.forge.cloudbees.com/release/</url>
</repository>
</repositories>
...
Vous remarquez que la dépendance à Open CSV a été mise en commentaire. Elle n'est plus nécessaire dans le fichier "pom.xml" car CSV Engine déclare déjà une dépendance vers Open CSV. Dans Eclipse, vous aurez donc toujours "opencsv-2.3.jar" car c'est CSV Engine qui va "tirer" cette bibliothèque. On parle de "dépendance transitive".
Relancez une compilation Maven. En fonction des éléments déjà présents sur votre machine, la réponse de Maven devrait ressembler à la trace suivante :
C:\TP\tp-chien-dao>
build
C:\TP\tp-chien-dao>
echo Build du TP Chien DAO
Build du TP Chien DAO
C:\TP\tp-chien-dao>
mvn clean install eclipse:eclipse
C:\TP\tp-chien-dao>
set JAVA_HOME
=
C:\dev\javas\jdk1.6
.0_24
[INFO] Scanning for
projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building TP Chien 1
.0
-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2
.4
.1
:clean (
default-clean) @ tp-chien-dao ---
[INFO] Deleting C:\TP\tp-chien-dao\target
[INFO]
[INFO] --- maven-resources-plugin:2
.4
.3
:resources (
default-resources) @ tp-chien-dao ---
[INFO] Using 'UTF-8'
encoding to copy filtered resources.
[INFO] Copying 2
resources
[INFO]
[INFO] --- maven-compiler-plugin:2
.3
.1
:compile (
default-compile) @ tp-chien-dao ---
[INFO] Compiling 11
source files to C:\TP\tp-chien-dao\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2
.4
.3
:testResources (
default-testResources) @ tp-chien-dao ---
[INFO] Using 'UTF-8'
encoding to copy filtered resources.
[INFO] Copying 3
resources
[INFO]
[INFO] --- maven-compiler-plugin:2
.3
.1
:testCompile (
default-testCompile) @ tp-chien-dao ---
[INFO] Compiling 4
source files to C:\TP\tp-chien-dao\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2
.9
:test (
default-test) @ tp-chien-dao ---
[INFO] Surefire report directory: C:\TP\tp-chien-dao\target\surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.icauda.tp.chien.dao.csv.AdvanceCsvChienDaoTest
...
Tests run: 9
, Failures: 0
, Errors: 0
, Skipped: 0
, Time elapsed: 0
.223
sec
Running com.icauda.tp.chien.dao.csv.NaiveCsvChienDaoTest
...
Tests run: 9
, Failures: 0
, Errors: 0
, Skipped: 0
, Time elapsed: 0
.08
sec
Running com.icauda.tp.chien.dao.csv.OpenCsvChienDaoTest
...
Tests run: 9
, Failures: 0
, Errors: 0
, Skipped: 0
, Time elapsed: 0
.057
sec
Results :
Tests run: 27
, Failures: 0
, Errors: 0
, Skipped: 0
[INFO]
[INFO] --- maven-jar-plugin:2
.3
.1
:jar (
default-jar) @ tp-chien-dao ---
[INFO] Building jar: C:\TP\tp-chien-dao\target\TP chien.jar
[INFO]
[INFO] --- maven-install-plugin:2
.3
.1
:install (
default-install) @ tp-chien-dao ---
[INFO] Installing C:\TP\tp-chien-dao\target\TP chien.jar to C:\dev\mavens\repository\com\icauda\tp-chien-dao\1
.0
-SNAPSHOT\tp-chien-dao-1
.0
-SNAPSHOT.ja
r
[INFO] Installing C:\TP\tp-chien-dao\pom.xml to C:\dev\mavens\repository\com\icauda\tp-chien-dao\1
.0
-SNAPSHOT\tp-chien-dao-1
.0
-SNAPSHOT.pom
[INFO]
[INFO] maven-eclipse-plugin:2
.8
:eclipse (
default-cli) @ tp-chien-dao
[INFO]
[INFO] maven-eclipse-plugin:2
.8
:eclipse (
default-cli) @ tp-chien-dao
[INFO]
[INFO] --- maven-eclipse-plugin:2
.8
:eclipse (
default-cli) @ tp-chien-dao ---
[INFO] Using Eclipse Workspace: null
[INFO] Adding default classpath container: org.eclipse.jdt.launching.JRE_CONTAINER
[INFO] File C:\TP\tp-chien-dao\.project already exists.
Additional settings will be preserved, run mvn eclipse:clean if
you want old settings to be removed.
[INFO] Wrote Eclipse project for
"tp-chien-dao"
to C:\TP\tp-chien-dao.
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 12
.745s
[INFO] Finished at: Tue Nov 13
21
:01
:28
CET 2012
[INFO] Final Memory: 14M/53M
[INFO] ------------------------------------------------------------------------
Bien entendu, faites un "refresh" dans Eclipse. Vérifiez que "csvengine-1.3.5.jar" est bien apparu dans la liste de vos dépendances.
Sans surprise, vous devez d'abord préparer un nouveau DAO et une nouvelle série de tests associée :
public
class
EngineCsvChienDao implements
CsvChienDao {
private
static
final
Logger LOGGER =
Logger.getLogger
(
EngineCsvChienDao.class
);
private
File file;
private
List<
Chien>
chiens;
private
Map<
String, Chien>
chienMapByNom;
private
List<
String>
entetes;
@Override
public
List<
Chien>
findAllChiens
(
) {
throw
new
UnsupportedOperationException
(
"Bientot"
);
}
@Override
public
Chien findChienByNom
(
String nom) {
throw
new
UnsupportedOperationException
(
"Bientot"
);
}
private
void
reloadChiens
(
) {
throw
new
UnsupportedOperationException
(
"Bientot"
);
}
@Override
public
void
init
(
File file) {
LOGGER.debug
(
"init"
);
this
.file =
file;
reloadChiens
(
);
}
@Override
public
File getFile
(
) {
return
file;
}
@Override
public
List<
String>
getEntetes
(
) {
return
entetes;
}
}
Comme ça commence à être du connu, je vais un peu plus vite…
public
class
EngineCsvChienDaoTest extends
AbstractCsvChienDaoTest {
private
static
final
Logger LOGGER =
Logger.getLogger
(
EngineCsvChienDaoTest.class
);
public
EngineCsvChienDaoTest
(
) {
LOGGER.debug
(
"Constructeur..."
);
dao =
new
EngineCsvChienDao
(
);
}
}
Profitez-en pour lancer les nouveaux tests, qui doivent être rouges.
Ne soyez pas choqué, vous allez maintenant faire évoluer le modèle :
import
fr.ybonnel.csvengine.annotation.CsvColumn;
import
fr.ybonnel.csvengine.annotation.CsvFile;
...
@CsvFile
(
separator=
";"
)
public
class
SimpleChien implements
Chien {
@CsvColumn
(
"Nom"
)
private
String nom;
...
Le cœur de Engine CSV est la classe CsvEngine :
import
fr.ybonnel.csvengine.CsvEngine;
...
public
class
EngineCsvChienDao implements
CsvChienDao {
...
private
void
reloadChiens
(
) {
LOGGER.debug
(
"reloadChiens"
);
final
CsvEngine engine =
new
CsvEngine
(
SimpleChien.class
);
throw
new
UnsupportedOperationException
(
"Bientot"
);
}
...
Vous pouvez maintenant faire le traitement directement :
public
class
EngineCsvChienDao implements
CsvChienDao {
...
private
void
reloadChiens
(
) {
LOGGER.debug
(
"reloadChiens"
);
try
{
final
CsvEngine engine =
new
CsvEngine
(
SimpleChien.class
);
final
FileInputStream fis =
new
FileInputStream
(
file);
List<
SimpleChien>
dogs =
engine.parseInputStream
(
fis, SimpleChien.class
).getObjects
(
);
}
catch
(
Exception e) {
LOGGER.error
(
"Une erreur s'est produite..."
, e);
}
}
...
Vous êtes obligés de passer par SimpleChien et une variable temporaire, puis la "caster" dans le bon type. On parle de "type erasure".
...
private
void
reloadChiens
(
) {
LOGGER.debug
(
"reloadChiens"
);
try
{
final
CsvEngine engine =
new
CsvEngine
(
SimpleChien.class
);
final
FileInputStream fis =
new
FileInputStream
(
file);
final
List<
? extends
Chien>
dogs =
engine.parseInputStream
(
fis, SimpleChien.class
).getObjects
(
);
chiens =
(
List<
Chien>
) dogs;
chienMapByNom =
new
HashMap<
String, Chien>(
dogs.size
(
));
for
(
Chien chien : chiens) {
LOGGER.debug
(
"[chien] "
+
chien);
chienMapByNom.put
(
chien.getNom
(
), chien);
}
}
catch
(
Exception e) {
LOGGER.error
(
"Une erreur s'est produite..."
, e);
}
}
Pour bien faire, complétez les autres méthodes simples :
...
@Override
public
List<
Chien>
findAllChiens
(
) {
LOGGER.debug
(
"findAllChiens"
);
if
(
chiens ==
null
) {
throw
new
IllegalStateException
(
"La liste n'a pas encore ete initialisee..."
);
}
return
chiens;
}
@Override
public
Chien findChienByNom
(
final
String nom) {
if
(
nom ==
null
||
nom.isEmpty
(
)) {
throw
new
IllegalArgumentException
(
"Le nom ne peut pas etre vide."
);
}
if
(
chiens ==
null
) {
throw
new
IllegalStateException
(
"La liste n'a pas encore ete initialisee..."
);
}
return
chienMapByNom.get
(
nom);
}
CSV Engine ne gère pas bien les commentaires dans les fichiers CSV. Vous devez donc les enlever de "chiens-01.csv", faute de quoi la bibliothèque vous renverra une exception… Attention aussi aux lignes "blanches" qui contiennent des espaces, et ne sont donc pas tout à fait vides…
Si toutefois vous ne souhaitez pas supprimer les lignes de commentaire du fichier, vous pouvez utiliser la solution suivante, qui a été proposée par le créateur de CSV Engine :
import
fr.ybonnel.csvengine.CsvEngine;
import
fr.ybonnel.csvengine.adapter.AdapterCsv;
import
fr.ybonnel.csvengine.factory.AbstractCsvReader;
import
fr.ybonnel.csvengine.factory.DefaultCsvManagerFactory;
import
fr.ybonnel.csvengine.factory.OpenCsvReader;
...
public
class
EngineCsvChienDao implements
CsvChienDao {
...
private
void
reloadChiens
(
) {
LOGGER.debug
(
"reloadChiens"
);
try
{
final
CsvEngine engine =
new
CsvEngine
(
SimpleChien.class
);
setEngineFactory
(
engine);
...
private
void
setEngineFactory
(
final
CsvEngine engine) {
engine.setFactory
(
new
DefaultCsvManagerFactory
(
) {
@Override
public
AbstractCsvReader createReaderCsv
(
Reader reader, char
separator) {
return
new
OpenCsvReader
(
reader, separator) {
@Override
public
String[] readLine
(
) throws
IOException {
String[] nextLine =
super
.readLine
(
);
if
(
isLineAComment
(
nextLine)) {
nextLine =
readLine
(
);
}
return
nextLine;
}
private
boolean
isLineAComment
(
String[] line) {
return
line !=
null
&&
line.length >
0
&&
line[0
].startsWith
(
"#"
);
}
}
;
}
}
);
}
...
Relancez les tests. Pour le moment, seuls "testCinqChiens" et "testPremierEstMilou" devraient être verts. C'est normal puisque vous n'avez pas encore annoté les autres attributs.
@CsvFile
(
separator=
";"
)
public
class
SimpleChien implements
Chien {
@CsvColumn
(
"Nom"
)
private
String nom;
@CsvColumn
(
"Nom complet"
)
private
String nomComplet;
...
@CsvColumn
(
value =
"race"
, adapter =
AdapterRace.class
)
private
RaceDeChien race;
...
import
fr.ybonnel.csvengine.adapter.AdapterCsv;
...
public
enum
RaceDeChien {
...
public
static
class
AdapterRace extends
AdapterCsv<
RaceDeChien>
{
@Override
public
RaceDeChien parse
(
String chaine) {
return
RaceDeChien.valueOfByCode
(
chaine);
}
@Override
public
String toString
(
RaceDeChien race) {
return
race.getCode
(
);
}
}
...
Relancez les tests et vérifiez que "testRacePluto" passe au vert.
Programmez l'"adapter" nécessaires au fonctionnement de l'attribut "sexe".
Vous ne pouvez pas modifier la classe "Double" pour y ajouter un "adapter" pour l'attribut "poids". Vous devez ajouter cet "adapter" dans le DAO directement. CSV Engine propose bien un "adapter" dédié, mais il n'accepte pas le format français (avec une virgule).
public
class
EngineCsvChienDao implements
CsvChienDao {
...
public
static
class
AdapterPoids extends
AdapterCsv<
Double>
{
@Override
public
Double parse
(
String chaine) {
chaine =
chaine.replace
(
','
, '.'
);
return
new
Double
(
chaine);
}
@Override
public
String toString
(
Double value) {
return
value.toString
(
);
}
}
...
public
class
SimpleChien implements
Chien {
...
@CsvColumn
(
"Nom"
)
private
String nom;
@CsvColumn
(
"Nom complet"
)
private
String nomComplet;
@CsvColumn
(
value =
"sexe"
, adapter =
AdapterSexe.class
)
private
Sexe sexe;
@CsvColumn
(
value =
"race"
, adapter =
AdapterRace.class
)
private
RaceDeChien race;
@CsvColumn
(
value =
"couleurs"
, adapter =
AdapterCouleurs.class
)
private
List<
String>
couleurs;
@CsvColumn
(
value =
"poids"
, adapter =
AdapterPoids.class
)
private
Double poids;
...
Relancez les tests et vérifiez que les tests basés sur le poids passent au vert.
Programmez "AdapterCouleurs" nécessaire au fonctionnement de l'attribut "couleurs".
Normalement, seuls les tests liés aux titres doivent rester en échec.
Un point (très) gênant est que cette façon de faire a introduit une dépendance de votre domaine vers le DAO et c'est moche… Pour y remédier, il suffit de créer des "vraies" classes :
package
com.icauda.tp.chien.domain.adapter;
import
fr.ybonnel.csvengine.adapter.AdapterCsv;
public
class
AdapterPoids extends
AdapterCsv<
Double>
{
@Override
public
Double parse
(
String chaine) {
chaine =
chaine.replace
(
','
, '.'
);
return
new
Double
(
chaine);
}
@Override
public
String toString
(
Double value) {
return
value.toString
(
);
}
}
import
com.icauda.tp.chien.domain.RaceDeChien.AdapterRace;
import
com.icauda.tp.chien.domain.Sexe.AdapterSexe;
import
com.icauda.tp.chien.domain.adapter.AdapterCouleurs;
import
com.icauda.tp.chien.domain.adapter.AdapterPoids;
...
@CsvFile
(
separator =
";"
)
public
class
SimpleChien implements
Chien {
...
Il ne manque plus que la liste des titres que vous devez programmer pour finir la partie sur les fichiers CSV. Cette partie est relativement simple, mais va vous demander de consulter la documentation de CSV Engine. Si vous n'y arrivez pas rapidement, vous avez (exceptionnellement) le droit de regarder la solution dans les annexes.
N'oubliez pas de lancer les tests, de formater votre code, d'organiser les imports, de documenter…
Le code du programme, à ce stade, est disponible dans le fichier Zip tp-chien-dao-02.zipFichier tp-chien-dao-02.zip (28 ko).
Vous venez de programmer quatre versions différentes d'un DAO dédié à la lecture d'un fichier CSV. Si on laisse de côté la version naïve, vous avez donc trois DAO qui se ressemblent beaucoup. Et pour cause puisque vous avez usé et abusé du copier-coller. La conséquence qui saute aux yeux, c'est que vous avez une grosse quantité de code dupliqué, qui diminue d'autant le niveau de qualité de votre application.
À l'aide d'héritage et/ou de classes utilitaires, vous devez réorganiser (on dira "refactorer") le code des trois DAO (la version naïve va finir à la poubelle) pour limiter les duplications. Je vous propose une solution "rapide" en annexe et dans le fichier Zip tp-chien-dao-03.zipFichier tp-chien-dao-03.zip (28 ko).
Dessinez un diagramme de classe UML, incluant les trois DAO "refactorés" et leurs liens. Ce travail est à faire au stylo et à rendre à la fin de la séance.
V. Conclusion▲
Ce TD est relativement complet, bien qu'on puisse aller encore plus loin. Il vous fait découvrir de nombreuses technologies, qui vous seront utiles au quotidien et dont vous devez maîtriser les grands principes : logs, gestion des ressources, tests, etc.
Pour aller plus loin, vous pouvez refaire ce travail, non plus avec des fichiers CSV mais avec une base de données. Je vous conseille les bases MySql (base classique) et HSQL DB (base en mémoire), ainsi que les bibliothèques de tests spécifiques pour les bases DbUnit et Unitils. Vous pouvez, par exemple, créer une version JDBC et une version JPA.
Vos retours nous aident à améliorer nos publications. N'hésitez donc pas à commenter cet article sur le forum : 7 commentaires
Ce TP est utilisé par de nombreux étudiants. N'hésitez donc pas à m'envoyer directement des idées d'amélioration, des propositions d'exercices supplémentaires, des pistes intéressantes, etc.
VI. Remerciements▲
Je tiens à remercier, en tant qu'auteur de TP, toutes les personnes qui m'ont aidé et soutenu. Je pense tout d'abord à mes collègues qui subissent mes questions au quotidien, mais aussi à mes contacts et amis du Web, dans le domaine de l'informatique ou non, qui m'ont fait part de leurs remarques et critiques. Bien entendu, je n'oublie pas l'équipe de Developpez.com qui m'a guidé dans la rédaction de cet article et m'a aidé à le corriger et le faire évoluer, principalement sur le forum.
Plus particulièrement j'adresse mes remerciements à Yan Bonnel (@ybonnel créateur français de CSV Engine), Mickael BARON (keulkeul), djibril, Sébastien Germez (FirePrawn), f-leb, Maxime Gault (_Max_), Philippe Leménager (CinePhil), Nemek et Claude Leloup.
VII. Annexes▲
VII-A. Liens▲
Maven : http://maven.apache.org/
JUnit : http://junit.org/
Eclipse : http://eclipse.org/
Log4j : http://logging.apache.org/log4j
Open CSV : http://opencsv.sourceforge.net/
CSV Engine : https://github.com/ybonnel/CsvEngine/wiki
Unitils : http://www.unitils.org/
DB Unit : http://www.dbunit.org/
JDBC : http://www.oracle.com/technetwork/java/javase/jdbc/index.html
JPA : http://www.oracle.com/technetwork/java/javaee/tech/persistence-jsp-140049.html
VII-B. Pour aller plus loin▲
Article "Importer un projet Maven dans Eclipse en 5 minutes" :
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/importer-projet-maven-dans-eclipse-5-min/
Article "Utiliser Maven 2" :
https://matthieu-lux.developpez.com/tutoriels/java/maven/
Article "Charger des données depuis un fichier CSV simple en 5 minutes" :
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/charger-donnees-fichier-csv-5-min/
Article "Les fichiers CSV avec Java" :
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/csv-avec-java/
Article "Charger des données depuis une base MySQL avec JDBC en 5 minutes" :
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/charger-donnees-mysql-5-min/
Article "Introduction à JPA, application au chargement de données depuis une base MySQL" :
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/charger-donnees-mysql-jpa-intro/
Article "3T en pratique, application au calcul de la suite de Fibonnaci, en 5 minutes" :
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/fibonacci-3t-5-min/
Cours "JPA (Java Persistence API)" :
https://jmdoudoux.developpez.com/cours/developpons/java/chap-jpa.php
Même si j'espère que vous n'en avez pas besoin, voici un très bon article de Jean-Michel DOUDOUX, pour "apprendre" Java :
https://jmdoudoux.developpez.com/cours/developpons/java/
Voici aussi une série d'articles (en plusieurs chapitres), dédiée à la préparation de la certification Java, grâce à laquelle vous en apprendrez certainement plus qu'avec n'importe quel autre support :
https://armel-ndjobo.developpez.com/memocertifjava6/
Retrouvez ma page et mes autres articles sur developpez.com à l'adresse
https://thierry-leriche-dessirier.developpez.com/#page_articlesTutoriels
Ajoutez-moi à vos contacts à l'aide du QR Code suivant :
Suivez-moi sur Twitter : @thierryleriche@thierryleriche
VII-C. FAQ▲
Cette section traite des questions qu'on me pose souvent. Merci de la lire avant de demander l'aide du professeur.
VII-C-1. M2_REPO▲
S'il vous manque les bibliothèques (cf. capture) ou qu'Elipse ne trouve pas le "Repositoty" Maven, c'est sans doute qu'il n'est pas configuré.
Dans ce cas, il faut aller dans les préférences à l'aide du menu "Window/Préférences". La suite dépend de la version d'Eclipse que vous utilisez.
Si vous n'avez pas de section "Maven" dans les préférences, alors allez dans "Java/Build Path/Classpath Variables". Ajoutez une variable à l'aide du bouton "New". Dans la popop qui s'ouvre, précisez "M2_REPO" en nom de variable. En valeur, vous devez indiquer le dossier où se trouve le "repository" de Maven. Par défaut, Maven enregistre les bibliothèques qu'il télécharge dans le dossier "[votre home]/.m2/repository". C'est ce dossier que doit référencer la variable M2_REPO.
Il est possible de changer le dossier du "repository" Maven. Pour cela, il faut modifier le fichier "settings.xml" qui se trouve dans le dossier "conf" dans le dossier où est installé Maven sur le disque. Personnellement, je préfère avoir un seul repository sur mon disque dur, et non un par utilisateur (comme c'est donc le cas par défaut), pour éviter de télécharger plusieurs fois les mêmes bibliothèques, ce qui sauve un peu (beaucoup) d'espace sur le disque dur. Voici un extrait de ma configuration :
<settings ..>
...
<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ~/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->
<localRepository>
C:\dev\mavens\repository</localRepository>
Si vous avez une section "Maven" dans les préférences, alors allez dans "Maven/User settings" où vous pouvez indiquer l'emplacement, sur le disque, où se trouve le Maven que vous utilisez, et plus spécifiquement le fichier de configuration "settings.xml".
En effet, dans les nouvelles versions d'Eclipse, il y a un plugin pour Maven et on ne peut pas modifier la variable M2_REPO (marquée "non modifiable") soi-même. En revanche, le plugin configure la variable M2_REPO en fonction de l'emplacement de Maven qu'on aura indiqué.
VII-C-2. Je ne trouve pas le dossier .m2 dans l'explorateur de fichiers▲
Le dossier ".m2/repository" est l'endroit où Maven stocke toutes les bibliothèques qu'il a téléchargées. Ce dossier est généralement placé dans le "HOME" de l'utilisateur (à la racine de votre compte sous Linux, ou dans "Mes Documents" sous Windows). Le "HOME" est référencé par les variables d'environnement "$HOME" sous X et "%USERPROFILE%" sous Windows. Pour que ce dossier existe, il faut avoir lancé une commande Maven, telle que "mvn clean install", au moins une fois.
Sous Linux, les dossiers commençant par un point sont des dossiers cachés. Vous pouvez les voir dans l'explorateur de fichier, quand vous configurez "M2_REPO" dans Eclipse par exemple, à l'aide de la combinaison de touches [Ctrl] + [H]. Note : J'ai testé ce raccourci sous Nautilus. Il est possible qu'il ne fonctionne pas sous Konqueror.
Si vous voulez définir un dossier spécifique et non caché, contrairement à ".m2", il faut configurer le fichier "settings.xml" dans Maven, et plus spécifiquement la clé "localRepository". Voici à quoi ça ressemble chez moi :
<settings ...>
<localRepository>
C:\dev\mavens\repository</localRepository>
...
VII-C-3. Je n'arrive pas à installer mon ordinateur portable▲
Consultez le chapitre "Installer un environnement de développement à la maison".
VII-C-4. Le sommaire prend trop de place▲
Vous pouvez réduire la table des matières (sommaire) dans la version HTML en ligne, si elle prend trop de place, à l'aide de la petite flèche.
VII-C-5. J'ai un warning "the import com.foo.truc is never used"▲
Si vous avez un warning vous indiquant qu'un package, que vous avez importé, n'est pas utilisé, ce n'est pas grave. Toutefois, je vous conseille de faire le ménage dans les imports qui ne servent à rien. Dans Eclipse, le plus simple est d'utiliser la combinaison [Ctrl] + [Maj] + [O]. C'est "O" comme "Organize" et pas comme "zéro".
VII-C-6. J'ai un warning "the serializable class Foo does not declare a static final serialVersionUID field of type long"▲
C'est un warning. Ce n'est pas grave. Cliquez sur le triangle jaune (avec le bouton de gauche) et prenez l'option "Add generated version id" dans le menu contextuel qui apparaît. Ça doit vous ajouter une variable ressemblant au code suivant : :
public
class
Foo ... {
private
static
final
long
serialVersionUID =
-
8600160150321329182
L;
...
VII-C-7. Comment savoir la taille d'un tableau ou d'une liste en Java ?▲
En Java, il suffit d'utiliser la propriété "lenght" pour savoir la taille d'un tableau :
String[] tab =
...
int
taille =
tab.length;
Pour savoir la taille d'une liste, il faut utiliser la méthode "size()" :
List<
String>
list =
...
int
taille =
list.size
(
);
"length" et "size()" vous donnent la taille d'un tableau et d'une liste, c'est-à-dire le nombre de "cases" ou d'items qu'ils contiennent. Ça ne vous dit pas si ces cases sont remplies (ie. si le contenu est différent de "null").
VII-C-8. Comment configurer le proxy de Maven▲
Les sociétés et les écoles protègent souvent le réseau (et l'accès Internet) à l'aide d'un proxy. Vous devez donc configurer Maven pour qu'il utilise ce proxy, faute de quoi Maven sera incapable de télécharger les bibliothèques nécessaires.
Voici un exemple avec la configuration de l'ESIEA. Vous devez renseigner les éléments suivants dans le fichier settings.xml :
<settings ...>
...
<proxies>
<proxy>
<id>
ESIEA</id>
<active>
true</active>
<protocol>
http</protocol>
<host>
proxy2.esiea.fr</host>
<port>
8080</port>
</proxy>
...
</proxies>
VII-C-9. Les travaux sont-ils à rendre ?▲
Oui. Chaque élève (ou binôme) doit rendre une copie. Les travaux demandés doivent être réalisés au stylo sur papier. Merci d'indiquer vos NOM, Prénom et classe en haut à gauche.
VII-D. Installer un environnement de développement à la maison▲
L'école vous fournit des machines déjà configurées. La plupart du temps, à part la variable "M2_REPO" dans Eclipse, il n'y a rien à faire de plus. C'est toutefois une bonne idée d'installer votre ordinateur pour pouvoir travailler à la maison. Je précise que les séances de TP ne sont pas dédiées à l'installation de votre machine. Si vous venez avec votre propre ordinateur à l'école, il doit être installé et configuré avant le début de la séance de TP.
Notez bien que votre professeur n'assure pas de support pour vos propres installations.
Pour commencer, je vous conseille de ne jamais installer vos logiciels de développement dans des dossiers contenant des caractères spéciaux ou des espaces. Ce premier conseil a son importance, car Windows vous propose généralement d'installer vos logiciels dans "Program Files". Dans la suite, je vais vous proposer d'installer vos logiciels dans le dossier "C:/dev". C'est comme ça que j'ai installé sur MON ordinateur portable.
VII-D-1. Installation de Java▲
Comme il est possible d'avoir plusieurs versions de Java installées en même temps sur un ordinateur, je vous conseille de les installer dans le dossier "C/dev/javas" où le "s" à la fin de "javas" prend tout son sens. Pensez à ne pas installer Java dans "Program Files".
Pour développer vous avez besoin d'une version "JDK" et non d'une simple version "JRE". Je vous conseille d'avoir une version de Java assez proche de celle utilisée pour ce TP (ie. 1.6.x, donc ni 1.5.X ni 1.7.x). Souvenez-vous que vous pouvez avoir plusieurs versions, tant qu'une seule n'est utilisée à la fois. Il faut commencer les installations par les versions les plus vieilles. Par exemple, sur MON PC portable, j'ai les versions :
- "1.4.2.19" installée dans "C:/dev/javas/j2sdk1.4.2_19" ;
- "1.5.0.20" installée dans "C:/dev/javas/jdk1.5.0_20" ;
- "1.6.0.24" installée dans "C:/dev/javas/jdk1.6.0_24" ;
- "1.6.0.24" en "32 bits" installée dans "C:/dev/javas/jdk1.6.0_24_32bits" ;
- "1.7.0.03" installée dans "C:/dev/javas/jdk1.7.0_03" ;
- …
Au moment où j'écris cet article, la page de téléchargement de Java chez Oracle (ne jamais la prendre ailleurs) est :
http://www.oracle.com/technetwork/java/javase/downloads/index.html
Notez bien que Java est disponible en "32 bits" et en "64 bits". Si vous avez un ordinateur récent, je vous conseille la version "64 bits".
Après avoir installé chaque version, ouvrez une nouvelle console et vérifiez qu'elle est prise en compte :
C:\>java -version
java version "1.7.0_03"
Java
(
TM) SE Runtime Environment (
build 1
.7
.0_03-b05)
Java HotSpot
(
TM) 64
-Bit Server VM (
build 22
.1
-b02, mixed mode)
VII-D-2. Installation d'Eclipse▲
Eclipse se présente sous la forme d'une archive Zip qu'il suffit de décompresser au bon endroit sur votre disque dur. Dans chaque version d'Eclipse, il y a plusieurs déclinaisons. Je vous conseille de télécharger la "Eclipse IDE for Java EE Developers" ou la "Eclipse IDE for Java Developers". Notez bien qu'Eclipse est disponible en "32 bits" et en "64 bits". Si vous avez un ordinateur récent, je vous conseille la version "64 bits". Cette dernière remarque est valable pour le JDK.
Au moment où j'écris cet article, la page de téléchargement d'Eclipse sur le site officiel (ne jamais la prendre ailleurs) est :
http://www.eclipse.org/downloads/
Comme il est possible d'avoir plusieurs versions de Maven en même temps sur un ordinateur, je vous conseille de les décompresser dans le dossier "C/dev/eclipses" où le "s" à la fin de "eclipses" prend tout son sens. Pensez juste à ne pas installer Java dans "Program Files".
À titre d'exemple, le Eclipse que j'utilise le plus souvent est installé dans le dossier "C:/dev/eclipses/eclipse-jee-indigo-win32-x86_64" sur MON ordinateur portable.
Pour lancer Eclipse, il suffit de double-cliquer sur "eclipse.exe". Je vous conseille de vous faire un raccourci.
Au lancement d'Eclipse, il vous demande où vous souhaitez mettre votre "workspace" sur le disque. Sur MON portable, j'ai plusieurs "workspaces". Depuis Eclipse, on peut passer très facilement d'un "workspace" à l'autre. À titre d'exemple, le "workspace" que j'utilise pour écrire le code de mes articles sur Developpez.com est "D:/workspaces/workspace-article-dvp-03".
Il est important de comprendre que le "workspace" correspond à la configuration de votre Eclipse. Avoir plusieurs "workspaces" permet de changer de configuration facilement. Cette configuration inclut par exemple la liste des projets ouverts dans Eclipse.
Il est aussi très important de comprendre que l'emplacement du "workspace" et l'emplacement de vos projets sur le disque sont deux choses différentes. Votre "workspace" ne doit pas inclure vos sources. Une bonne raison à cela est qu'un même projet peut être ouvert dans plusieurs "workspaces". Ainsi, sur MON portable, mes "workspaces" sont dans "D:/workspaces" et mes projets Java sont dans "D:/javadev".
Sur MON ordinateur portable, j'installe les logiciels sur le disque "C" et je place les données sur le disque "D". Quand je réinstalle mon Windows, je ne formate que le disque "C". Du coup mes données sont toujours présentes sur "D". Je précise que je n'ai en réalité d'un seul disque dur (physiquement) dans MON portable. Les disques "C" et "D" sont en fait des partitions.
VII-D-3. Installation de Maven▲
Maven se présente sous la forme d'une archive Zip qu'il suffit de décompresser au bon endroit sur votre disque dur.
Au moment où j'écris cet article, la page de téléchargement de Maven chez Apache (ne jamais la prendre ailleurs) est :
http://maven.apache.org/download.cgi
Comme il est possible d'avoir plusieurs versions de Maven en même temps sur un ordinateur, je vous conseille de les décompresser dans le dossier "C/dev/mavens" où le "s" à la fin de "mavens" prend tout son sens. Pensez juste à ne pas installer Java dans "Program Files".
Vous devez ensuite configurer votre variable d'environnement "PATH" en incluant le dossier "bin" de votre Maven. Par exemple, sur MON ordinateur, Maven est installé dans le dossier "C:/dev/mavens/apache-maven-3.0.3". J'ai donc ajouté le chemin "C:/dev/mavens/apache-maven-3.0.3/bin" dans la variable d'environnement "Path".
Pour modifier les variables d'environnement sous Windows, vous pouvez utiliser le raccourci [Windows] + [Pause]. Attention, sur les portables, il faut souvent compléter avec la touche bleue [Fn]. Dans la popup qui s'ouvre, choisissez "Paramètres système avancés" (à gauche sous Windows 7). Dans la nouvelle popup, cliquez sur le bouton "Variables d'environnement". Dans la popup il faut alors chercher "Path" dans la zone du bas…
Ouvrez une nouvelle console et tapez "mvn --version" pour vérifier que c'est bien pris en compte.
C:\>mvn --version
Apache Maven 3
.0
.3
(
r1075438; 2011
-02
-28
18
:31
:09
+0100
)
Maven home: C:\dev\mavens\apache-maven-3
.0
.3
\bin\..
Java version: 1
.6
.0_24, vendor: Sun Microsystems Inc.
Java home: C:\dev\javas\jdk1.6
.0_24\jre
Default locale: fr_FR, platform encoding: Cp1252
OS name: "windows 7"
, version: "6.1"
, arch: "amd64"
, family: "windows"
Si vous faites attention, vous remarquez que ma version de Maven n'utilise pas ma version de Java par défaut "1.7.0_03" (cf. plus haut), mais la version "1.6.0_24". Pour faire ça, j'ai ajouté une instruction en haut du fichier "mvn.bat" (dans le dossier "bin") de Maven :
echo Maven 3
de Thierry
set JAVA_HOME
=
C:\dev\javas\jdk1.6
.0_24
Pour indiquer un emplacement spécifique pour le repository (là où Maven stocke les bibliothèques téléchargées), il faut configurer le fichier "settings.xml" :
<settings ...>
<localRepository>
C:\dev\mavens\repository</localRepository>
...
VII-D-4. FAQ de Developpez.com▲
Je vous encourage vivement à consulter les FAQ de Developpez.com : https://www.developpez.com/faq/
VII-E. Ancienne procédure de compillation et d'import dans Eclipse▲
VII-E-1. Anciène procédure avec Maven▲
Durée estimée : 2 minutes.
Nous allons utiliser Maven en ligne de commande pour compiler le projet d'exemple et créer les fichiers Eclipse dont nous aurons besoin dans le chapitre suivant.
Ouvrez une console (terminal). Pour cela, sous Windows, dans le menu "Démarrer", utilisez la commande "cmd". Ça doit vous ouvrir une popup noire, qui vous place dans votre dossier personnel. Allez dans le dossier "C:\TP\tp-chien-dao" qu'on a créé plus tôt.
Le fichier Maven "pom.xml" décrit la structure du projet d'exemple. Ouvrez-le dans un éditeur de texte comme Ultra Edit (ou Notepad++ ou PsPad mais surtout pas Word ou Bloc-notes) pour découvrir comment il est fait. Voici un résumé des parties importantes à ce stade.
<project
xmlns
=
"http://maven.apache.org/POM/4.0.0"
...>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.icauda</groupId>
<artifactId>
tp-chien-dao</artifactId>
<version>
1.0-SNAPSHOT</version>
<packaging>
jar</packaging>
<name>
TP Chien</name>
<properties>
<!-- Construction du projet -->
<project.build.sourceEncoding>
UTF-8</project.build.sourceEncoding>
<java.version>
1.6</java.version>
<maven-compiler-plugin.version>
2.3.1</maven-compiler-plugin.version>
<!-- Lib de test -->
<junit.version>
4.8.2</junit.version>
<!-- Lib de log -->
<log4j.version>
1.2.13</log4j.version>
...
</properties>
<dependencies>
<!-- Junit -->
<dependency>
<groupId>
junit</groupId>
<artifactId>
junit</artifactId>
<version>
${junit.version}</version>
<scope>
test</scope>
</dependency>
<!-- log4j -->
<dependency>
<groupId>
log4j</groupId>
<artifactId>
log4j</artifactId>
<version>
${log4j.version}</version>
</dependency>
</dependencies>
...
</project>
Le fichier "pom.xml" indique ici une dépendance aux bibliothèques "jUnit" en version "4.8.2" et "Log4j" en version "1.2.13".
Les versions choisies ("4.8.2" et "1.2.13") sont tout simplement les dernières qui étaient disponibles quand j'ai écrit (ou mis à jour) ce document.
Compilez maintenant le projet à l'aide de la commande Maven suivante :
mvn clean install
L'option "clean" ne sert à rien à ce stade, mais prenez l'habitude de l'utiliser.
En fonction des éléments déjà présents sur l'ordinateur, l'exécution de la commande devrait ressembler à la trace suivante :
C:\TP\tp-chien-dao>
mvn clean install
...
[INFO] Scanning for
projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building TP Chien 1
.0
-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2
.4
.1
:clean (
default-clean) @ tp-chien-dao ---
[INFO] Deleting C:\TP\tp-chien-dao\target
[INFO]
[INFO] --- maven-resources-plugin:2
.4
.3
:resources (
default-resources) @ tp-chien-dao ---
[INFO] Using 'UTF-8'
encoding to copy filtered resources.
[INFO] Copying 1
resource
[INFO]
[INFO] --- maven-compiler-plugin:2
.3
.1
:compile (
default-compile) @ tp-chien-dao ---
[INFO] Compiling 8
source files to C:\TP\tp-chien-dao\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2
.4
.3
:testResources (
default-testResources) @ tp-chien-dao ---
[INFO] Using 'UTF-8'
encoding to copy filtered resources.
[INFO] Copying 3
resources
[INFO]
[INFO] --- maven-compiler-plugin:2
.3
.1
:testCompile (
default-testCompile) @ tp-chien-dao ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2
.9
:test (
default-test) @ tp-chien-dao ---
[INFO] Surefire report directory: C:\TP\tp-chien-dao\target\surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Results :
Tests run: 0
, Failures: 0
, Errors: 0
, Skipped: 0
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3
.000s
[INFO] Finished at: Tue Oct 23
13
:34
:52
CEST 2012
[INFO] Final Memory: 8M/105M
[INFO] ------------------------------------------------------------------------
C:\TP\tp-chien-dao>
La trace "Final Memory: 8M/105M" indique simplement la mémoire utilisée sur MON ordinateur. La valeur dépend de nombreux facteurs, comme les autres logiciels (Word, Photoshop, etc.) lancés en même temps. En toute logique, la consommation mémoire sera donc différente d'un ordinateur à l'autre.
Voici une capture d'écran de ma console à ce stade :
Au passage, vous pouvez constater que le dossier "target" a été créé. Il contient la version compilée du projet.
Créez maintenant les fichiers Eclipse à l'aide de la commande Maven suivante :
mvn eclipse:eclipse
En fonction des éléments déjà présents sur l'ordinateur, l'exécution de la commande devrait ressembler à la trace suivante :
C:\TP\tp-chien-dao> mvn eclipse:eclipse
...
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building TP Chien 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] maven-eclipse-plugin:2.8:eclipse (default-cli) @ tp-chien-dao
[INFO]
[INFO] maven-eclipse-plugin:2.8:eclipse (default-cli) @ tp-chien-dao
[INFO]
[INFO] --- maven-eclipse-plugin:2.8:eclipse (default-cli) @ tp-chien-dao ---
[INFO] Using Eclipse Workspace: null
[INFO] Adding default classpath container: org.eclipse.jdt.launching.JRE_CONTAINER
[INFO] Wrote settings to C:\TP\tp-chien-dao\.settings\org.eclipse.jdt.core.prefs
[INFO] Wrote Eclipse project for "tp-chien-dao" to C:\TP\tp-chien-dao.
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.604s
[INFO] Finished at: Tue Oct 23 13:35:34 CEST 2012
[INFO] Final Memory: 6M/76M
[INFO] ------------------------------------------------------------------------
C:\TP\tp-chien-dao>
Voici une capture d'écran de ma console à ce stade :
Déterminez à quoi sert Maven. Vous pouvez attendre la fin du TP pour répondre à cette question si c'est plus facile pour vous.
On aurait pu lancer les deux commandes "mvn clean install" et "mvn eclipse:eclipse" en une seule fois, pour gagner du temps :
mvn clean install eclipse:eclipse
Les fichiers "build.bat" et "build.sh" servent simplement de raccourcis pour lancer toutes les commandes Maven sans avoir à tout taper.
VII-E-2. Ancienne procédure d'import dans Eclipse▲
Durée estimée : 1 minute.
Lancez Eclipse si ce n'est pas déjà fait. Eclipse devrait ressembler à la capture d'écran suivante :
Les zones (package, outline, console, etc.) qui composent le logiciel peuvent être disposées à votre convenance.
Importez le projet dans Eclipse. Pour cela, utilisez le menu "File/Import", qui ouvre la popup suivante :
Choisissez l'option "General/Existing projects into workspace". Dans la suite, sélectionnez simplement le projet sur le disque.
De retour sur la fenêtre principale d'Eclipse, vous pouvez distinguer la structure des projets Java-Maven :
- les dossiers "src/test/java" et "src/test/resources" contiennent respectivement des tests écrits en Java et des fichiers de configuration (ressources) pour les tests ;
- les dossiers "src/main/java" et "src/main/resources" contiennent le code du programme et les ressources associées ;
- le bloc "Referenced librairies" contient la liste des dépendances (bibliothèques) utilisées par le projet. Ce sont ces mêmes dépendances qui apparaissent dans le fichier "pom.xml" ;
- le bloc "JRE System Librairy" correspond simplement à la version de Java que vous utilisez ;
- enfin le dossier "target" (déjà présenté plus haut) contient la version compilée par Maven du projet.
Si les dépendances apparaissent en erreur, si le bloc est absent ou si vous avez des erreurs indiquant qu'il manque la variable « M2_REPO » (cf. capture d'écran et vidéo ci-dessous), c'est signe que tout n'est pas bien configuré. Consultez alors la FAQ (dans les annexes) et plus particulièrement les questions dédiées à Maven et "M2_REPO". Il est possible que la variable "M2_REPO" soit déjà présente mais non modifiable car est est bloquée par le plugin Maven (si installé). Dans ce cas, il faudra spécifier le bon fichier de setting au plugin Maven.
Vidéo de démo des étapes précédentes dans Eclipse Kepler, sans le plugin Maven (m2) :
Cliquez pour lire la vidéo
Vidéo de démo des étapes précédentes dans Eclipse Luna, avec le plugin Maven (m2) :
Cliquez pour lire la vidéo
Merci de tenir compte de l'avertissement précédent, à propos de "M2_REPO".
Merci de vraiment tenir compte de l'avertissement à propos de la variable "M2_REPO" dans Eclipse.
Cette procédure (Maven + Eclipse) a aussi été expliquée, avec d'autres mots, dans l'article "Importer un projet Maven dans Eclipse en 5 minutes", disponible à l'adresse https://thierry-leriche-dessirier.developpez.com/tutoriels/java/importer-projet-maven-dans-eclipse-5-min/ Vous pouvez donc consulter cet article si vous avez besoin d'une seconde explication.
Ouvrez la classe "Launcher" que vous trouverez dans le package "com.icauda.tp.chien". Cette classe contient une méthode "main(..)" et est donc exécutable.
Lancez l'exécution de la classe "Launcher". Pour cela, sélectionnez la classe puis cliquez sur l'icône en forme de triangle (cf. capture). Si tout est bon, vous devriez voir apparaître des lignes dans la "console".
Vous pouvez aussi lancer l'exécution à l'aide du bouton droit de la souris. Dans le menu contextuel qui apparaît, choisissez "Run as/Java application".
VII-F. Solution possible▲
<project
xmlns
=
"http://maven.apache.org/POM/4.0.0"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi
:
schemaLocation
=
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.icauda</groupId>
<artifactId>
tp-chien-dao</artifactId>
<version>
1.0-SNAPSHOT</version>
<packaging>
jar</packaging>
<name>
TP Chien</name>
<description>
TP avec des chiens.</description>
<url>
http://www.icauda.com</url>
<licenses>
<license>
<name>
Copyright ©1995-2013 icauda.com et Copyright ©2012 Developpez.com</name>
<comments>
Les sources présentés sur cette page sont libres de droits, et vous pouvez les utiliser à votre convenance.</comments>
</license>
</licenses>
<developers>
<!-- Thierry -->
<developer>
<name>
Thierry Leriche-Dessirier</name>
<roles>
<role>
Developper</role>
<role>
Professeur</role>
</roles>
<organization>
ICAUDA</organization>
</developer>
<!-- TODO Mettre votre nom ici, pour chaque membre de l'équipe. -->
<developer>
<name>
Prénom Nom</name>
<roles>
<role>
Etudiant</role>
</roles>
<organization>
Mon école</organization>
</developer>
</developers>
<properties>
<!-- Construction du projet -->
<project.build.sourceEncoding>
UTF-8</project.build.sourceEncoding>
<java.version>
1.6</java.version>
<maven-compiler-plugin.version>
2.3.1</maven-compiler-plugin.version>
<!-- Lib de test -->
<junit.version>
4.8.2</junit.version>
<unitils-dbunit.version>
3.3</unitils-dbunit.version>
<!-- DB -->
<mysql-connector-java.version>
5.1.18</mysql-connector-java.version>
<hsqldb.version>
2.2.8</hsqldb.version>
<!-- Lib de log -->
<log4j.version>
1.2.13</log4j.version>
<!-- Lib CSV -->
<opencsv.version>
2.3</opencsv.version>
<csvengine.version>
1.3.5</csvengine.version>
<!-- Libs pour les rapports -->
<maven-site-plugin.version>
3.0-beta-3</maven-site-plugin.version>
<maven-javadoc-plugin.version>
2.8</maven-javadoc-plugin.version>
<maven-jxr-plugin.version>
2.3</maven-jxr-plugin.version>
<taglist-maven-plugin.version>
2.4</taglist-maven-plugin.version>
<maven-surefire-plugin.version>
2.9</maven-surefire-plugin.version>
<maven-surefire-report-plugin>
2.9</maven-surefire-report-plugin>
</properties>
<dependencies>
<!-- Junit -->
<dependency>
<groupId>
junit</groupId>
<artifactId>
junit</artifactId>
<version>
${junit.version}</version>
<scope>
test</scope>
</dependency>
<!-- log4j -->
<dependency>
<groupId>
log4j</groupId>
<artifactId>
log4j</artifactId>
<version>
${log4j.version}</version>
</dependency>
<!-- Lib CSV -->
<dependency>
<groupId>
net.sf.opencsv</groupId>
<artifactId>
opencsv</artifactId>
<version>
${opencsv.version}</version>
</dependency>
<dependency>
<groupId>
fr.ybonnel</groupId>
<artifactId>
csvengine</artifactId>
<version>
${csvengine.version}</version>
</dependency>
<!-- Ci dessous des lib dont vous aurez besoin pour aller plus loin. -->
<!-- MySql et HSQLDB -->
<dependency>
<groupId>
mysql</groupId>
<artifactId>
mysql-connector-java</artifactId>
<version>
${mysql-connector-java.version}</version>
<scope>
compile</scope>
</dependency>
<dependency>
<groupId>
org.hsqldb</groupId>
<artifactId>
hsqldb</artifactId>
<version>
${hsqldb.version}</version>
<scope>
test</scope>
</dependency>
<!-- Unitils (avec DBUnit) -->
<dependency>
<groupId>
org.unitils</groupId>
<artifactId>
unitils-dbunit</artifactId>
<version>
${unitils-dbunit.version}</version>
<scope>
test</scope>
</dependency>
</dependencies>
<build>
<finalName>
TP chien</finalName>
<plugins>
<plugin>
<artifactId>
maven-compiler-plugin</artifactId>
<version>
${maven-compiler-plugin.version}</version>
<configuration>
<source>
${java.version}</source>
<target>
${java.version}</target>
<encoding>
${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<!-- Doc generee par Maven -->
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-site-plugin</artifactId>
<version>
${maven-site-plugin.version}</version>
</plugin>
<!-- Surefire (rapport de test) -->
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-surefire-plugin</artifactId>
<version>
${maven-surefire-plugin.version}</version>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<!-- Javadoc -->
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-javadoc-plugin</artifactId>
<version>
${maven-javadoc-plugin.version}</version>
</plugin>
<!-- JXR : pour lier les sources -->
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-jxr-plugin</artifactId>
<version>
${maven-jxr-plugin.version}</version>
</plugin>
<!-- Taglist : pour voir les TODO restant dans le code -->
<plugin>
<groupId>
org.codehaus.mojo</groupId>
<artifactId>
taglist-maven-plugin</artifactId>
<version>
${taglist-maven-plugin.version}</version>
<configuration>
<tagListOptions>
<tagClasses>
<tagClass>
<displayName>
Todo Work</displayName>
<tags>
<tag>
<matchString>
TODO</matchString>
<matchType>
ignoreCase</matchType>
</tag>
<tag>
<matchString>
FIXME</matchString>
<matchType>
exact</matchType>
</tag>
</tags>
</tagClass>
<tagClass>
<displayName>
Regles</displayName>
<tags>
<tag>
<matchString>
REGLE</matchString>
<matchType>
ignoreCase</matchType>
</tag>
</tags>
</tagClass>
</tagClasses>
</tagListOptions>
</configuration>
</plugin>
<!-- Surefire (tests JUnit) -->
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-surefire-report-plugin</artifactId>
<version>
${maven-surefire-report-plugin}</version>
</plugin>
</plugins>
</reporting>
<repositories>
<repository>
<id>
ybonnel-release</id>
<url>
https://repository-ybonnel.forge.cloudbees.com/release/</url>
</repository>
<repository>
<id>
ybonnel-snapshot</id>
<url>
https://repository-ybonnel.forge.cloudbees.com/snapshot/</url>
</repository>
</repositories>
</project>
Pensez à utiliser la commande "mvn site" pour générer la doc du projet.
package
com.icauda.tp.chien.dao;
import
java.util.List;
import
com.icauda.tp.chien.domain.Chien;
public
interface
ChienDao {
List<
Chien>
findAllChiens
(
);
Chien findChienByNom
(
final
String nom);
}
package
com.icauda.tp.chien.dao.csv;
import
java.io.File;
import
java.util.List;
import
com.icauda.tp.chien.dao.ChienDao;
public
interface
CsvChienDao extends
ChienDao {
public
void
init
(
File file);
public
File getFile
(
);
public
List<
String>
getEntetes
(
);
}
package
com.icauda.tp.chien.dao.csv;
import
java.io.File;
import
java.util.List;
import
java.util.Map;
import
org.apache.log4j.Logger;
import
com.icauda.tp.chien.domain.Chien;
public
abstract
class
AbstractCsvChienDao implements
CsvChienDao {
private
static
final
Logger LOGGER =
Logger.getLogger
(
AbstractCsvChienDao.class
);
protected
File file;
protected
List<
Chien>
chiens;
protected
Map<
String, Chien>
chienMapByNom;
protected
List<
String>
entetes;
protected
abstract
void
reloadChiens
(
);;
@Override
public
void
init
(
File file) {
LOGGER.debug
(
"init"
);
this
.file =
file;
reloadChiens
(
);
}
@Override
public
List<
Chien>
findAllChiens
(
) {
LOGGER.debug
(
"findAllChiens"
);
if
(
chiens ==
null
) {
throw
new
IllegalStateException
(
"La liste n'a pas encore ete initialisee..."
);
}
return
chiens;
}
@Override
public
Chien findChienByNom
(
final
String nom) {
if
(
nom ==
null
||
nom.isEmpty
(
)) {
throw
new
IllegalArgumentException
(
"Le nom ne peut pas etre vide."
);
}
if
(
chiens ==
null
) {
throw
new
IllegalStateException
(
"La liste n'a pas encore ete initialisee..."
);
}
return
chienMapByNom.get
(
nom);
}
@Override
public
File getFile
(
) {
return
file;
}
@Override
public
List<
String>
getEntetes
(
) {
return
entetes;
}
}
package
com.icauda.tp.chien.dao.csv;
import
java.io.BufferedReader;
import
java.io.FileReader;
import
java.io.IOException;
import
java.util.ArrayList;
import
java.util.HashMap;
import
java.util.List;
import
org.apache.log4j.Logger;
import
com.icauda.tp.chien.domain.Chien;
import
com.icauda.tp.chien.domain.RaceDeChien;
import
com.icauda.tp.chien.domain.Sexe;
import
com.icauda.tp.chien.domain.SimpleChien;
public
class
AdvancedCsvChienDao extends
AbstractCsvChienDao {
private
static
final
Logger LOGGER =
Logger.getLogger
(
AdvancedCsvChienDao.class
);
private
final
static
String SEPARATOR =
";"
;
private
List<
String>
getLignesFromFile
(
) {
LOGGER.debug
(
"getLignesFromFile"
);
final
List<
String>
lignes =
new
ArrayList<
String>(
);
FileReader fr =
null
;
BufferedReader br =
null
;
try
{
fr =
new
FileReader
(
file);
br =
new
BufferedReader
(
fr);
for
(
String ligne =
br.readLine
(
); ligne !=
null
; ligne =
br.readLine
(
)) {
// Suppression des espaces en trop
ligne =
ligne.trim
(
);
// Filtre des lignes vides
if
(
ligne.isEmpty
(
)) {
continue
;
}
// Filtre des lignes de commentaire
if
(
ligne.startsWith
(
"#"
)) {
continue
;
}
lignes.add
(
ligne);
}
}
catch
(
IOException e) {
LOGGER.error
(
"Lecture impossible"
, e);
}
finally
{
if
(
br !=
null
) {
try
{
br.close
(
);
}
catch
(
IOException e) {
LOGGER.error
(
"Fermeture impossible"
, e);
}
}
if
(
fr !=
null
) {
try
{
fr.close
(
);
}
catch
(
IOException e) {
LOGGER.error
(
"Fermeture impossible"
, e);
}
}
}
return
lignes;
}
protected
Chien transformLigneToChien
(
final
String[] values) throws
Exception {
LOGGER.debug
(
"transformLigneToChien"
);
final
SimpleChien chien =
new
SimpleChien
(
);
chien.setNom
(
values[0
]);
chien.setNomComplet
(
values[1
]);
final
String tempSexe =
values[2
];
final
Sexe sexe =
Sexe.valueOfByCode
(
new
Integer
(
tempSexe));
chien.setSexe
(
sexe);
final
String tempRace =
values[3
];
final
RaceDeChien race =
RaceDeChien.valueOfByCode
(
tempRace);
chien.setRace
(
race);
final
String tempCouleurs =
values[4
];
final
List<
String>
couleurs =
new
ArrayList<
String>(
);
final
String[] tempCouleurs2 =
tempCouleurs.split
(
","
);
for
(
String couleur : tempCouleurs2) {
couleurs.add
(
couleur);
}
chien.setCouleurs
(
couleurs);
final
String tempPoids =
values[5
];
Double poids =
new
Double
(
tempPoids.replace
(
','
, '.'
));
chien.setPoids
(
poids);
return
chien;
}
private
Chien transformLigneToChien
(
final
String ligne) throws
Exception {
LOGGER.debug
(
"transformLigneToChien"
);
final
String[] values =
ligne.split
(
SEPARATOR);
return
transformLigneToChien
(
values);
}
protected
void
transformEntetes
(
final
String[] tabEntetes) {
LOGGER.debug
(
"transformEntetes"
);
entetes =
new
ArrayList<
String>(
tabEntetes.length);
for
(
String entete : tabEntetes) {
entetes.add
(
entete);
}
}
private
void
transformEntetes
(
final
String ligneEntete) {
LOGGER.debug
(
"transformEntetes"
);
final
String[] tabEntetes =
ligneEntete.split
(
SEPARATOR);
transformEntetes
(
tabEntetes);
}
/**
* Chargement des chiens.
*/
@Override
protected
void
reloadChiens
(
) {
LOGGER.debug
(
"reloadChiens"
);
if
(
file ==
null
) {
throw
new
IllegalStateException
(
"Le fichier est nul..."
);
}
try
{
final
List<
String>
lignes =
getLignesFromFile
(
);
final
String ligneEntete =
lignes.remove
(
0
);
LOGGER.debug
(
"Entetes : "
+
ligneEntete);
transformEntetes
(
ligneEntete);
chiens =
new
ArrayList<
Chien>(
lignes.size
(
));
chienMapByNom =
new
HashMap<
String, Chien>(
lignes.size
(
));
for
(
String ligne : lignes) {
final
Chien chien =
transformLigneToChien
(
ligne);
chiens.add
(
chien);
chienMapByNom.put
(
chien.getNom
(
), chien);
}
}
catch
(
Exception e) {
LOGGER.error
(
"Une erreur s'est produite..."
, e);
}
}
}
package
com.icauda.tp.chien.dao.csv;
import
java.io.FileReader;
import
java.util.ArrayList;
import
java.util.HashMap;
import
java.util.List;
import
org.apache.log4j.Logger;
import
au.com.bytecode.opencsv.CSVReader;
import
com.icauda.tp.chien.domain.Chien;
public
class
OpenCsvChienDao extends
AdvancedCsvChienDao {
private
static
final
Logger LOGGER =
Logger.getLogger
(
OpenCsvChienDao.class
);
private
final
static
char
SEPARATOR =
';'
;
@Override
protected
void
reloadChiens
(
) {
LOGGER.debug
(
"reloadChiens"
);
if
(
file ==
null
) {
throw
new
IllegalStateException
(
"Le fichier est nul..."
);
}
try
{
final
List<
String[] >
lignes =
getLignesFromFile
(
);
final
String[] ligneEntete =
lignes.remove
(
0
);
transformEntetes
(
ligneEntete);
chiens =
new
ArrayList<
Chien>(
lignes.size
(
));
chienMapByNom =
new
HashMap<
String, Chien>(
lignes.size
(
));
for
(
String[] ligne : lignes) {
final
Chien chien =
transformLigneToChien
(
ligne);
chiens.add
(
chien);
chienMapByNom.put
(
chien.getNom
(
), chien);
}
}
catch
(
Exception e) {
LOGGER.error
(
"Une erreur s'est produite..."
, e);
}
}
private
List<
String[] >
getLignesFromFile
(
) {
LOGGER.debug
(
"getLignesFromFile"
);
if
(
file ==
null
) {
throw
new
IllegalStateException
(
"Le fichier est nul..."
);
}
final
List<
String[] >
lignes =
new
ArrayList<
String[] >(
);
try
{
final
FileReader fr =
new
FileReader
(
file);
final
CSVReader csvReader =
new
CSVReader
(
fr, SEPARATOR);
String[] nextLine =
null
;
while
((
nextLine =
csvReader.readNext
(
)) !=
null
) {
int
size =
nextLine.length;
// ligne vide
if
(
size ==
0
) {
continue
;
}
String debut =
nextLine[0
].trim
(
);
if
(
debut.isEmpty
(
) &&
size ==
1
) {
continue
;
}
// ligne de commentaire
if
(
debut.startsWith
(
"#"
)) {
continue
;
}
lignes.add
(
nextLine);
}
}
catch
(
Exception e) {
LOGGER.error
(
"aie aie aie"
, e);
}
return
lignes;
}
}
package
com.icauda.tp.chien.dao.csv;
import
java.io.FileInputStream;
import
java.io.IOException;
import
java.io.Reader;
import
java.util.ArrayList;
import
java.util.HashMap;
import
java.util.List;
import
org.apache.log4j.Logger;
import
com.icauda.tp.chien.domain.Chien;
import
com.icauda.tp.chien.domain.SimpleChien;
import
fr.ybonnel.csvengine.CsvEngine;
import
fr.ybonnel.csvengine.factory.AbstractCsvReader;
import
fr.ybonnel.csvengine.factory.DefaultCsvManagerFactory;
import
fr.ybonnel.csvengine.factory.OpenCsvReader;
import
fr.ybonnel.csvengine.model.Error;
import
fr.ybonnel.csvengine.model.Result;
public
class
EngineCsvChienDao extends
AbstractCsvChienDao {
private
static
final
Logger LOGGER =
Logger.getLogger
(
EngineCsvChienDao.class
);
private
void
setEngineFactory
(
final
CsvEngine engine) {
engine.setFactory
(
new
DefaultCsvManagerFactory
(
) {
@Override
public
AbstractCsvReader createReaderCsv
(
Reader reader, char
separator) {
return
new
OpenCsvReader
(
reader, separator) {
@Override
public
String[] readLine
(
) throws
IOException {
String[] nextLine =
super
.readLine
(
);
if
(
isLineAComment
(
nextLine)) {
nextLine =
readLine
(
);
}
return
nextLine;
}
private
boolean
isLineAComment
(
String[] line) {
return
line !=
null
&&
line.length >
0
&&
line[0
].startsWith
(
"#"
);
}
}
;
}
}
);
}
@Override
protected
void
reloadChiens
(
) {
LOGGER.debug
(
"reloadChiens"
);
try
{
final
CsvEngine engine =
new
CsvEngine
(
SimpleChien.class
);
setEngineFactory
(
engine);
final
FileInputStream fis =
new
FileInputStream
(
file);
final
Result<
SimpleChien>
resultat =
engine.parseInputStream
(
fis, SimpleChien.class
);
final
List<
SimpleChien>
dogs =
resultat.getObjects
(
);
chiens =
new
ArrayList<
Chien>(
dogs.size
(
));
chienMapByNom =
new
HashMap<
String, Chien>(
dogs.size
(
));
for
(
Chien chien : dogs) {
LOGGER.debug
(
"[chien] "
+
chien);
chiens.add
(
chien);
chienMapByNom.put
(
chien.getNom
(
), chien);
}
List<
Error>
errors =
resultat.getErrors
(
);
LOGGER.debug
(
errors);
entetes =
engine.getColumnNames
(
SimpleChien.class
);
LOGGER.debug
(
"[entetes] "
+
entetes);
}
catch
(
Exception e) {
LOGGER.error
(
"Une erreur s'est produite..."
, e);
}
}
}
package
com.icauda.tp.chien.domain;
import
java.util.List;
import
com.icauda.tp.chien.domain.RaceDeChien.AdapterRace;
import
com.icauda.tp.chien.domain.Sexe.AdapterSexe;
import
com.icauda.tp.chien.domain.adapter.AdapterCouleurs;
import
com.icauda.tp.chien.domain.adapter.AdapterPoids;
import
fr.ybonnel.csvengine.annotation.CsvColumn;
import
fr.ybonnel.csvengine.annotation.CsvFile;
@CsvFile
(
separator =
";"
)
public
class
SimpleChien implements
Chien {
private
static
final
long
serialVersionUID =
-
1225454238084424608
L;
@CsvColumn
(
value =
"Nom"
, order =
0
)
private
String nom;
@CsvColumn
(
value =
"Nom complet"
, order =
1
)
private
String nomComplet;
@CsvColumn
(
value =
"sexe"
, adapter =
AdapterSexe.class
, order =
2
)
private
Sexe sexe;
@CsvColumn
(
value =
"race"
, adapter =
AdapterRace.class
, order =
3
)
private
RaceDeChien race;
@CsvColumn
(
value =
"couleurs"
, adapter =
AdapterCouleurs.class
, order =
4
)
private
List<
String>
couleurs;
@CsvColumn
(
value =
"poids"
, adapter =
AdapterPoids.class
, order =
5
)
private
Double poids;
public
SimpleChien
(
) {
// rien...
}
public
SimpleChien
(
final
String nom) {
this
.nom =
nom;
}
public
SimpleChien
(
final
String nom, final
RaceDeChien race, final
Sexe sexe) {
this
(
nom);
this
.race =
race;
this
.sexe =
sexe;
}
@Override
public
String toString
(
) {
return
"SimpleChien [nom="
+
nom +
"]"
;
}
// + getters et setters
}
package
com.icauda.tp.chien.dao.csv;
import
static
org.junit.Assert.assertEquals;
import
static
org.junit.Assert.assertNotNull;
import
static
org.junit.Assert.assertNull;
import
static
org.junit.Assert.assertTrue;
import
java.io.File;
import
java.util.List;
import
org.apache.log4j.Logger;
import
org.junit.Assert;
import
com.icauda.tp.chien.domain.Chien;
import
com.icauda.tp.chien.domain.RaceDeChien;
public
abstract
class
AbstractCsvChienDaoTest {
private
static
final
Logger LOGGER =
Logger.getLogger
(
AbstractCsvChienDaoTest.class
);
public
final
static
String RESOURCES_PATH =
"src/test/resources/"
;
protected
CsvChienDao dao;
protected
void
prepare
(
final
String fileName) {
LOGGER.debug
(
"prepare"
);
final
File file =
new
File
(
RESOURCES_PATH +
fileName);
dao.init
(
file);
}
protected
void
doTestNbChiens
(
final
int
nombreChiensAttendus) {
// Act
final
List<
Chien>
chiens =
dao.findAllChiens
(
);
// Assert
assertEquals
(
nombreChiensAttendus, chiens.size
(
));
}
protected
void
doTestNbNom
(
final
int
position, final
String nomAttendu) {
// Act
final
List<
Chien>
chiens =
dao.findAllChiens
(
);
final
Chien chien =
chiens.get
(
position);
// Assert
assertEquals
(
nomAttendu, chien.getNom
(
));
}
protected
void
doTestNbPoids
(
final
int
position, final
Double poidsAttendu) {
// Act
final
List<
Chien>
chiens =
dao.findAllChiens
(
);
final
Chien chien =
chiens.get
(
position);
// Assert
assertEquals
(
poidsAttendu, chien.getPoids
(
));
}
protected
void
doTestNbCouleurs
(
final
int
position, final
String[] couleursAttendues) {
final
List<
Chien>
chiens =
dao.findAllChiens
(
);
final
Chien chien =
chiens.get
(
position);
// Assert
final
List<
String>
couleurs =
chien.getCouleurs
(
);
final
int
size =
couleurs.size
(
);
LOGGER.debug
(
"[size] "
+
size);
assertEquals
(
couleursAttendues.length, size);
// Test dans ordre
for
(
String couleurAttendue : couleursAttendues) {
assertTrue
(
couleurs.contains
(
couleurAttendue));
}
}
protected
void
doTestNbRace
(
final
int
position, final
RaceDeChien raceAttendue) {
// Act
final
List<
Chien>
chiens =
dao.findAllChiens
(
);
final
Chien chien =
chiens.get
(
position);
// Assert
assertEquals
(
raceAttendue, chien.getRace
(
));
}
protected
void
doTestTailleEntetes
(
final
int
tailleAttendue) {
// Act
final
List<
String>
entetes =
dao.getEntetes
(
);
// Assert
assertEquals
(
tailleAttendue, entetes.size
(
));
}
protected
void
doTestNbEntete
(
final
String enteteAttendu, final
int
position) {
// Act
final
List<
String>
entetes =
dao.getEntetes
(
);
final
String entete =
entetes.get
(
position);
// Assert
assertEquals
(
enteteAttendu, entete);
}
protected
void
doTestAllEntetes
(
final
String[] entetesAttendus) {
final
List<
String>
entetes =
dao.getEntetes
(
);
// Assert
// Ici je teste la taille, non pas pour verifier la taille, mais pour
// verifier que les deux listes
// contiennent le meme nombre d'elements. Ca m'evite de lancer deux
// boucles for.
Assert.assertEquals
(
entetesAttendus.length, entetes.size
(
));
for
(
int
i =
0
; i <
entetesAttendus.length; i++
) {
assertEquals
(
entetesAttendus[i], entetes.get
(
i));
}
}
protected
void
doTestRechercheChienByNom
(
final
String nom, final
Double poidsAttendu) {
// Act
final
Chien chien =
dao.findChienByNom
(
nom);
// Assert
assertNotNull
(
chien);
assertEquals
(
nom, chien.getNom
(
));
assertEquals
(
poidsAttendu, chien.getPoids
(
));
}
protected
void
doTestRechercheFailed
(
final
String nom) {
// Act
final
Chien chien =
dao.findChienByNom
(
nom);
// Assert
assertNull
(
chien);
}
}
package
com.icauda.tp.chien.dao.csv.csv01;
import
org.apache.log4j.Logger;
import
org.junit.Before;
import
org.junit.Test;
import
com.icauda.tp.chien.dao.csv.AbstractCsvChienDaoTest;
import
com.icauda.tp.chien.domain.RaceDeChien;
public
abstract
class
AbstractCsvChienDaoTest01 extends
AbstractCsvChienDaoTest {
private
static
final
Logger LOGGER =
Logger.getLogger
(
AbstractCsvChienDaoTest01.class
);
public
final
static
String CHIENS_FILE_NAME =
"chiens-01.csv"
;
@Before
public
void
doBefore
(
) {
LOGGER.debug
(
"doBefore Debut"
);
prepare
(
CHIENS_FILE_NAME);
LOGGER.debug
(
"doBefore Fin"
);
}
@Test
public
void
testCinqChiens
(
) {
LOGGER.debug
(
"testCinqChiens... Debut"
);
// Arrange
final
int
nombreChiensAttendus =
5
;
// Act and assert
doTestNbChiens
(
nombreChiensAttendus);
LOGGER.debug
(
"testCinqChiens... Fin"
);
}
@Test
public
void
testPremierEstMilou
(
) {
LOGGER.debug
(
"testPremierEstMilou... Debut"
);
// Arrange
final
int
position =
0
;
final
String nomAttendu =
"Milou"
;
// Act and assert
doTestNbNom
(
position, nomAttendu);
LOGGER.debug
(
"testPremierEstMilou... Fin"
);
}
@Test
public
void
testPoidsLassie
(
) {
LOGGER.debug
(
"testPoidsLassie... Debut"
);
// Arrange
final
int
position =
2
;
final
Double poidsAttendu =
32.3
;
// Act and assert
doTestNbPoids
(
position, poidsAttendu);
LOGGER.debug
(
"testPoidsLassie... Fin"
);
}
@Test
public
void
testCouleursVolt
(
) {
LOGGER.debug
(
"testCouleursVolt... Debut"
);
// Arrange
final
int
position =
3
;
final
String[] couleursAttendues =
{
"blanc"
, "noir"
}
;
// Act and assert
doTestNbCouleurs
(
position, couleursAttendues);
LOGGER.debug
(
"testCouleursVolt... Fin"
);
}
@Test
public
void
testRacePluto
(
) {
LOGGER.debug
(
"testRacePluto... Debut"
);
// Arrange
final
int
position =
1
;
final
RaceDeChien raceAttendue =
RaceDeChien.GOLDEN;
// Act and assert
doTestNbRace
(
position, raceAttendue);
LOGGER.debug
(
"testRacePluto... Fin"
);
}
@Test
public
void
testTailleEntetes
(
) {
LOGGER.debug
(
"testTailleEntetes... Debut"
);
// Arrange
final
int
tailleAttendue =
6
;
// Act and assert
doTestTailleEntetes
(
tailleAttendue);
LOGGER.debug
(
"testTailleEntetes... Fin"
);
}
@Test
public
void
testPremierEntetes
(
) {
LOGGER.debug
(
"testPremierEntetes... Debut"
);
// Arrange
final
String enteteAttendu =
"Nom"
;
final
int
position =
0
;
// Act and assert
doTestNbEntete
(
enteteAttendu, position);
LOGGER.debug
(
"testTailleEntetes... Fin"
);
}
@Test
public
void
testAllEntetes
(
) {
LOGGER.debug
(
"testAllEntetes... Debut"
);
// Arrange
final
String[] entetesAttendus =
{
"Nom"
, "Nom complet"
, "sexe"
, "race"
, "couleurs"
, "poids"
}
;
// Act and assert
doTestAllEntetes
(
entetesAttendus);
LOGGER.debug
(
"testAllEntetes... Fin"
);
}
@Test
public
void
testRechercheLassie
(
) {
LOGGER.debug
(
"testRechercheLassie... Debut"
);
// Arrange
final
String nom =
"Lassie"
;
final
Double poidsAttendu =
32.3
;
// Act and assert
doTestRechercheChienByNom
(
nom, poidsAttendu);
LOGGER.debug
(
"testRechercheLassie... Fin"
);
}
@Test
public
void
testRechercheIdefix
(
) {
LOGGER.debug
(
"testRechercheIdefix... Debut"
);
// Arrange
final
String nom =
"Idefix"
;
// Act and assert
doTestRechercheFailed
(
nom);
LOGGER.debug
(
"testRechercheIdefix... Fin"
);
}
}
package
com.icauda.tp.chien.dao.csv.csv01;
import
org.apache.log4j.Logger;
import
com.icauda.tp.chien.dao.csv.EngineCsvChienDao;
public
class
EngineCsvChienDaoTest01 extends
AbstractCsvChienDaoTest01 {
private
static
final
Logger LOGGER =
Logger.getLogger
(
EngineCsvChienDaoTest01.class
);
public
EngineCsvChienDaoTest01
(
) {
LOGGER.debug
(
"Constructeur..."
);
dao =
new
EngineCsvChienDao
(
);
}
}