Test d'interaction^

Définition^

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

Exercice 1^

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.

Doublures^

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:

Outillage^

Le site des mocks : http://www.mockobjects.com

Frameworks existants:

Principes d'utilisation de JMock^

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:

  1. on crée les mocks, on décrit leur comportement;
  2. on crée une instance à tester en utilisant ces mocks, on exécute l'appel à tester sur cette instance;
  3. on vérifie que les attentes sont bien vérifiées (vérification qui peut être implicite).
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();

Description d'un mock (expectations)^

À 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):

  1. combien de fois doit avoir lieu l'appel: au moins une (atLeastOne()), jamais (never()), exactement une (once()), exactement n (exactly(int))
  2. avec quels paramètres:
    • paramètre égal à X (eq(), same());
    • paramètre vérifiant une contrainte donnée:
      • null, non null, contient une chaîne donnée...
      • importe quelle valeur;
      • le et, ou, non logique de contraintes;
      • possibilité de dédcrire ses propres contraintes.
  3. concernant la terminaison:
    • quelle valeur retourne la méthode (obligatoire si pas de type void: il faut dire au mock quelle valeur sera effectivement retournée lors de l'appel):
      • à chaque appel qui satisfait les conditions précédentes (returnValue);
      • pour des appels successifs (ex: retourne 1 au 1er apple, puis 2 au 2nd, puis 3 au 3ème) (onConsecutiveCall);
      • ou quelle exception elle lance (throwException).
  4. concernant le séquencement des appels : si l'appel doit se produire après un autre appel donné (possibilité de nommer les appels).

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;

Contraintes^

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:

Exercices^

Exercice 3:^

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 ?

Exercice 4^

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:

Exercice 5^

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();
    }

Conséquences^

Couplage faible^

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).

Comportement explicite^

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:

Dépendance à l'implantation^

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.

Vérifie ce qui est fait et pas fait^

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).

Behaviour Driven Development^

Principes^

Technique de développement inspirée du TDD mais basée sur le test du comportement des objets:

Outils^

Java
Ruby
Python

JBehave^

Deux frameworks pour le prix d'un:

Behaviours^

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:

Principale 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.

Story^

Une Narration (Story) est définie par:

Un Scenario est défini par:

Test d'intégration^

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 1^

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.

Exemple 2^

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^

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 :