Une première définition
Une seconde définition.
Pour ce TD on utilisera ds un premier temps la seconde définition.
En écrivant les mocks à la main
Sur le test de l'appel de la méthode Jeu::jouer(10), écrire les mocks pour et tester que:
Fastidieux ! Il existe des outils qui créent automatiquement des mocks.
Principe général de la définition de Doublures: objets créés uniquement pour les besoins du test en remplacement d'objets réels.
Les différences entre bouchons et simulacres:
Le site des mocks : http://www.mockobjects.com
Frameworks existants:
Construit comme une extension de JUnit : on n'utilise plus
junit.framework.TestCase mais org.jmock.MockObjectTestCase.
Permet de créer un mock:
La description d'un mock consiste en ses expectations:
Étapes à suivre:
Mock mockDe1 = new Mock(De.class);
Les attentes sont à vérifier explicitement par mockDe1.verify().
ou encore :
Mock mockDe1 = mock(De.class); // méthode de MockObjectTestCase
Dans ce cas l'appel de verify() est implicite. Dans tous les cas l'utilisation du mock se fait comme suit:
Jeu j = new Jeu((De) mockDe1.proxy(),....); jeu.jouer();
À un mock peuvent être attachées plusieurs descriptions.
Chaque description concerne le/les appels d'une méthode. Pour cette méthode on précise (ou pas) les détails suivants (dans l'ordre):
atLeastOne()), jamais (never()), exactement une (once()),
exactement n (exactly(int))eq(), same());returnValue);onConsecutiveCall);throwException).jMock permet de définir les attentes (expectations) sous une forme "naturel": l'expression de l'attente en Java est très proche d'une expression similaire en anglais:
// on attend 1 appel de la methode verify avec n,importe quel
// parametre et on retourne alors 1
mock.expects(once()).method("verify").with(ANYTHING).will(returnValue(1));
// sans la ponctutaion:
// mock expects once method "verify" with ANYTHING will returnValue 1;
JMock définit des contraintes génériques pour la vérification des
paramètres passés au mock object. Il est possible d'en créer de
nouvelles en passant à la méthode with() un objet Constraint:
describeTo passe un StringBuffer dans lequel la
description de la contrainte est écriteeval(Object) invoque la contrainte sur un paramètre
donné.Mocker un flot d'entrée On considère la méthode suivante:
public void ecritFlotDansFichier(FileWriter f, InputStream in, int nb)
throws IOException,FlotTropCourtException {
for (int i = 0; i< nb; i++) {
int lu = in.read();
if (lu == -1)
throw new FlotTropCourtException();
else f.write(lu);
}
f.close();
}
Quels mocks ? Quelles attentes ? Quels cas de test ?
On considère l'interface AbstractBD suivante:
public IdConnexion connecte(User u) throws DejaConnecteException; public void deconnecte(IdConnexion id) throws PasConnecteException; public String select(IdConnexion id,String s) throws UnknownId ;
et la classe UserBD suivante:
public UserBD(); public void setBD(AbstractBD bd); public void connexionBD() throws DejaConnecteException; public void deconnexionBD() throws PasConnecteException; public void select(String s) throws UnknownId;
Test de UserBD: Décrire les mocks et un cas de test qui teste:
On s'intéresse au comportement de la classe Jeu dans la bataille
navale. Cette classe est supposée régler l'interaction entre les deux
joueurs. Ecrire les tests unitaires pour cette classe.
private boolean gameOver() { boolean fini1 = this.joueur1.aPerdu(); boolean fini2 = this.joueur2.aPerdu(); boolean fini = fini1 || fini2; } private void andTheWinnerIs() { if (this.joueur1.aPerdu()) System.out.println ("Le joueur 1 a perdu."); else if (this.joueur2.aPerdu()) System.out.println ("Le joueur 2 a perdu."); else System.out.println("Problème !!!"); } public void jouer() { int nbTour = 1; while (! gameOver()) { System.out.println ("Tour " + nbTour); System.out.println ("Au joueur 1 de jouer"); joueur1.jouer(joueur2); System.out.println ("Au joueur 2 de jouer"); joueur2.jouer(joueur1); nbTour ++; } andTheWinnerIs(); }
Voir aussi Test objet. L'utilisation de ces techniques de test, en particulier en lien avec le TDD a des conséquences importantes sur la conception des classes et des systèmes:
Les objets forment un réseau dont chaque connexion est modifiable (pas nécessairement dynamiquement, mais suffisamment pour permettre d'être testée).
Pour écrire correctement les attentes et le résultat de l'invocation d'un mock, il faut connaître avec suffisamment de précision le comportement spécifié de l'objet:
L'implantation est 'déduite' ou induite par la description du mock: les interactions avec les autres objets induisent nécessairement une manière de réaliser l'implantation.
Le mock vérifie que tout ce qui est prévu à lieu et que rien de ce qui n'est pas prévu n'a lieu: comportement strict ! Plus simple que lorsqu'on utilise une inner class ou classe anonyme (nécessité d'une variable d'état pour vérifier que les appels ont été faits).
Technique de développement inspirée du TDD mais basée sur le test du comportement des objets:
Deux frameworks pour le prix d'un:
Une classe = un ensemble de comportements pour un objet Une méthode = un comportement spécifique à vérifier.
Même principe de fonctionnement que pour JUnit:
UsingMiniMock ce qui lui permet
d'utiliser le framework de Mock objects intégré de JBehavesetUp est invoquée pour construire l'état du systèmePrincipale différence: accent mis sur le comportement, on teste en fonction de ce que devrait faire l'objet (ie. utilisation de should) et pas en fonction de son état.
Une Narration (Story) est définie par:
Un Scenario est défini par:
Given <the world has some property>When <I do something>Then <The world has changed>Test un ensemble d'unités entre elles pour vérifier leur bonne intégration. Test une structure d'objets (CUT) particulière correspondant à une ou plusieurs situation en production. Test configuration probable d'interactions entre objets.
Hypothèse:
Pb : état des objets partagés entre plusieurs instances: objets doivent coopérer entre eux pour respecter les spécifications des objets qu'ils partagent (ou ne partager que des objets sans états ni effets de bord).
+-----------+ +------------+
| A | | B |
+--+--------+ +------|-----+
| |
| |
| |
+----->+---------------+ |
| C |<------
+---------------+
Exemple d'un DAB "intelligent" qui cache le solde d'un compte pour accélérer les opérations.
class DAB {
DAB() {
this.compte = CompteFactory.getCompte(...);
this.solde = this.compte.getSolde();
}
public void retirer(int sum ){
if(this.solde - retrait > sum) {
this.retrait += sum;
distribuer(sum);
} else
display.show("Solde insuffisant");
}
public void retraitCarte() {
this.compte.debiter(this.retrait);
}
}
Si l'instance de compte est partagée, on peut autoriser des retraits avec un solde insuffisant.
Lecteur de données: utilise une connexion à un flux de données pour lire au fur et à mesure des entiers.
class DBReader {
DB is;
DBReader(DB is) {
this.is = is;
}
public int getData() {
return is.read();
}
public void done() {
is.close();
}
}
class DB {
public void open() {
...
}
public int read() {
if(!closed)
try {
return is.read();
}catch(IOException e) {
throw new RuntimeException(e);
}
else
throw new RuntimeException("cannot read, db is closed");
}
public void close() {
this.closed = true;
is.close();
}
}
test d'intégration: construit des cas de test simulant les interactions entre objets et validant leur bonne coopération.
objectif : identifier les conflits possibles dans l'utilisation des objets (et du système)
solutions :