Semaine 11: Mock Objects - semiria/INF2015 GitHub Wiki
##Objectifs
- Expérimenter l'injection de dépendance
- Expérimenter les mock objects
##Exercices
Score est un petit programme servant à enregistrer des scores dans un fichier.
La classe principale, PanneauScore, est responsable de savoir quel est le score actuel et délègue l'écriture du score à l'objet de type Panneau.
PanneauScore
public class PanneauScore {
final private String EMPLACEMENT = "./output/";
final private String NOM_FICHIER = "scoreboard.txt";
final private String ENCODAGE = "utf-8";
private int score = 0;
private Panneau panneau;
public PanneauScore () throws UnsupportedEncodingException, FileNotFoundException {
panneau = new Panneau(EMPLACEMENT + NOM_FICHIER, ENCODAGE);
}
public void inscrireScore(int resultat) throws FileNotFoundException, IOException {
this.score = this.score + resultat;
panneau.ouvrirPanneau();
panneau.ecrireDansPanneau(Integer.toString(this.score));
panneau.fermerPanneau();
}
public int getScore() {
return score;
}
}
La classe Panneau est responsable d'écrire le score dans un fichier déterminé par son appelant.
Panneau
public class PanneauScore {
final private String EMPLACEMENT = "./output/";
final private String NOM_FICHIER = "scoreboard.txt";
final private String ENCODAGE = "utf-8";
private int score = 0;
private Panneau panneau;
public PanneauScore () throws UnsupportedEncodingException, FileNotFoundException {
panneau = new Panneau(EMPLACEMENT + NOM_FICHIER, ENCODAGE);
}
public void inscrireScore(int resultat) throws FileNotFoundException, IOException {
this.score = this.score + resultat;
panneau.ouvrirPanneau();
panneau.ecrireDansPanneau(Integer.toString(this.score));
panneau.fermerPanneau();
}
public int getScore() {
return score;
}
}
Et la classe PanneauScoreTest teste si PanneauScore enregistre les bons scores selon des scénarios déterminés.
PanneauScoreTest
public class PanneauScoreTest {
int pointage = 5;
//Quand nous commençons une partie
//Lorsqu'on enregistre un premier pointage
//Alors le panneau possède le nouveau pointage
@Test
public void testNouvellePartieQuandOnScoreAlorsLePointageAugmente() throws IOException, Throwable {
PanneauScore leaderboard = new PanneauScore();
leaderboard.inscrireScore(pointage);
int nouveauPointage = leaderboard.getScore();
assertEquals(pointage, nouveauPointage);
}
//Quand nous avons une partie commencée
//Et qu'il y a déjà un score d'inscrit
//Lorsqu'on enregistre un nouveau pointage
//Alors le panneau possède le nouveau pointage
@Test
public void testPartieExistanteQuandOnScoreAlorsLePointageAugmente() throws IOException, Throwable {
PanneauScore leaderboard = new PanneauScore();
leaderboard.inscrireScore(pointage);
leaderboard.inscrireScore(pointage);
int nouveauPointage = leaderboard.getScore();
assertEquals(pointage*2, nouveauPointage);
}
//Quand commençons une nouvelle partie
//Et qu'aucun point n'est scoré avant la fin de la partie
//Alors le pointage est de 0
@Test
public void testNouvellePartieQuandOnNeScorePasAlorsLePointageEstZero() throws IOException, Throwable {
PanneauScore leaderboard = new PanneauScore();
int nouveauPointage = leaderboard.getScore();
assertEquals(0, nouveauPointage);
}
}
Vous remarquez qu'en exécutant les tests unitaires, un fichier scoreboard.txt
est créé dans le répertoire ./output
. Ceci est problématique étant donné que les tests unitaires ne devraient pas accéder au disque dur.
Attention! Si l'exécution de vos tests terminent avec testNouvellePartieQuandOnNeScorePasAlorsLePointageEstZero, le fichier ne sera pas créé.
###Injection de dépendance Nous remarquons que la classe PanneauScore créé elle-même un nouvel objet de type EcriveurPanneau, qui lui fait un accès au disque dur pour vérifier qu'il n'existe pas déjà un fichier existant et l'efface au besoin.
public PanneauScore () throws UnsupportedEncodingException, FileNotFoundException {
panneau = new Panneau(EMPLACEMENT + NOM_FICHIER, ENCODAGE);
}
public Panneau (String fileName, String encoding) {
nomFichier = fileName;
encodage = encoding;
effacerAncienPanneau();
}
private void effacerAncienPanneau() {
File vieuxPanneau = new File(nomFichier);
if(vieuxPanneau.exists()) {
vieuxPanneau.delete();
}
}
Les tests unitaires sur la classe PanneauScore devraient seulement se préoccuper de tester les responsabilités de la classe, c'est-à-dire si elle envoie le bon score ou non à l'objet Panneau. L'accès au disque dur devrait être exclut des tests.
Deux étapes seront nécessaires afin de rendre les tests unitaires indépendants. La première étape sera d'injecter l'objet Panneau au constructeur de la classe PanneauScore.
public PanneauScore (IPanneau panneau) throws UnsupportedEncodingException, FileNotFoundException {
this.panneau = panneau;
}
N'oubliez pas d'ajuster les tests unitaires!
###Mock Object La seconde étape est de créer un nouvel objet de type Panneau qui sera utilisé par les tests unitaires, ce sera un mock object.
Nous allons extraire une interface à partir de la classe Panneau, et utiliser cette interface dans la classe PanneauScore.
En utilisant les outils de refactoring de NetBeans, vous pouvez exécuter la fonction Extract Interface afin d'obtenir rapidement le résultat suivant:
public class Panneau implements IPanneau {
...
}
public interface IPanneau {
void ecrireDansPanneau(String message) throws UnsupportedEncodingException, FileNotFoundException, IOException;
void fermerPanneau() throws IOException;
void ouvrirPanneau() throws UnsupportedEncodingException, FileNotFoundException;
}
Modifier ensuite l'objet Panneau de la classe PanneauScore afin d'inclure un objet qui implémente l'interface IPanneau.
private IPanneau panneau;
public PanneauScore (IPanneau panneau) throws UnsupportedEncodingException, FileNotFoundException {
this.panneau = panneau;
this.panneau = new Panneau(EMPLACEMENT + NOM_FICHIER, ENCODAGE);
}
Créer une nouvelle classe PanneauMock qui implémente l'interface IPanneau, et en faire son implémentation.
public class PanneauMock implements IPanneau {
@Override
public void ecrireDansPanneau(String message) throws UnsupportedEncodingException, FileNotFoundException, IOException {
...
}
@Override
public void fermerPanneau() throws IOException {
...
}
@Override
public void ouvrirPanneau() throws UnsupportedEncodingException, FileNotFoundException {
...
}
}
Modifier les tests unitaires afin d'utiliser la classe PanneauMock
@Test
public void testNouvellePartieQuandOnScoreAlorsLePointageAugmente() throws IOException, Throwable {
PanneauMock panneau = null;
PanneauScore leaderboard = new PanneauScore(panneau);
...
}
Lorsque vous exécutez les tests, vous remarquerez maintenant qu'aucun fichier n'est créé dans le répertoire ./output
. Vous avez maintenant des tests unitaires qui sont indépendants du disque dur.
##Solutionnaire Les différentes étapes de l'exercice sont représentées dans une branche.
- Initial: https://github.com/semiria/Score/
- Injection dépendance: https://github.com/semiria/Score/tree/injection-dependance
- Mock Object: https://github.com/semiria/Score/tree/mock-object