Skip to content

Technique de test : le Mocking

Olivier Martin edited this page Sep 21, 2017 · 2 revisions

Le mocking

Qu'est-ce que le mocking

Le mocking est un ensemble de technique, d'API, qui vont permettre de contrôler le comportement d'un objet que l'on veut tester. On peut traduire le verbe anglais to mock par imiter, copier. On va donc utiliser le mocking pour copier, voir changer le comportement d'un objet et ainsi pouvoir faire des test dessus. On pourra par exemple vérifier l'appel de certaines méthodes, forcer la valeur de retour d'une fonction ou encore lancer une exception lorsqu'une méthode est appelée.

Mockito

L'API que nous avons décider d'utiliser est Mockito. Cette librairie permet de faire la plupart des choses dont nous avons besoin. Cependant, parfois nous devons faire des choses qui sont limitée chez Mockito. Lorsque nous devrons contourner ces limitations, nous utiliserons PowerMock.

Les bases

Real mocking et partial mocking

On va distinguer deux types de mocking

  1. Le full mocking. Lorsque l'on crée un objet en faisant du full mocking, le comportement de toutes les méthodes vont être modifiées pour renvoyer une valeur par défaut (par exemple la valeur par défaut des types) ou une valeur enregistrée. On ne garde donc pas le comportement de l'objet. Avec Mockito, pour créer un mock object par full mocking, on peut utiliser cette commande
ObjectToMock mockObject = Mockito.mock(ObjectToMock.class);
  1. Le partial mocking. Lorsque l'on crée un objet en faisant du partial mocking, le comportement des méthodes n'est pas modifié sauf si explicitement demandé. Avec Mockito, pour créer un mock object par partial mocking, on peut utiliser cette commande
ObjectToMock mockObject = Mockito.spy(ObjectToMock.class);

Cas d'utilisation

Tout au long de cette partie, nous utiliseront comme exemple des bout de code pris de missions de ce projet. Pour garder une concision, nous ne mettons pas l'entièreté de la méthode de test. Chaque fois nous indiquerons le dossiers dans lequel vous pouvez retrouver les tests.

De plus, nous ne prétendons pas avoir une technique parfaite. L'utilisations que nous faisons des tests unitaires est très spécifique. Tout les retours pour améliorer nos techniques sont bien venus.

Vérifier qu'une méthode a été appelée

Nous prendrons, pour ce premier cas d'utilisation, l'exemple de la question de démarrage numéro 2 de la mission 6. (dossier m06dem2). Pour ce faire, nous allons suivre la procédure suivante

  1. Créer un mock object
  2. Appeler le code de l'étudiant
  3. Utiliser la méthode Mockito.verify

Ici, nous ne précisons pas si nous faisons du full ou du partial mocking car cela dépend de vos tests. Dans l'exemple qui suit, nous faisons du partial mocking, mais ça n'a aucun impact sur la technique. Voici le code associé:

// OrderedPair est la classe dont nous voulons vérifier le comportement
OrderedPair spy = Mockito.spy(OrderedPair.class); // Etape 1
String feed = "Vous devez utiliser setOrdered!";
spy.setA(-2); // Etape 2
try {
    Mockito.verify(spy,atLeast(1)).setOrdered(false); // Etape 3
} catch (WantedButNotInvoked e) {
    fail(feed);
}

Le code est assez intuitif (via le nom des méthode). La ligne "Etape 3" peut se lire comme "Je vérifie que sur l'objet spy, on a appelé au moins 1 fois la méthode setOrdered avec comme paramètre false".

Vérifier la création d'un objet

Pour ce cas-ci, nous prendrons comme exemple la question 7 de la mission 9 (dossier m09Q7). L'idée ici est de forcer l'étudiant à utiliser un BufferedReader et de vérifier qu'un objet de ce type est en effet créer. Pour faire cela, la marche à suivre est la suivante

  1. Changer le comportement du constructeur du BufferedReader pour qu'il renvoie un mock object à la place d'un vrai BufferedReader
  2. Lancer le code de l'étudiant
  3. Vérifier qu'un BufferedReader a bien été appelé

Si nous prenons le code Java associé (Remarquons ici que nous devons utilisez PowerMock car nous voulons mocker la création d'un objet)

try {
    PowerMockito.whenNew(BufferedReader.class).withParameterTypes(Reader.class)
                .withArguments(Mockito.any(Reader.class)).thenReturn(PowerMockito.mock(BufferedReader.class)); // Etape 1
    int [] v = Etudiant.readVector("./file1"); // Etape 2
    PowerMock.verifyNew(BufferedReader.class).withArguments(Mockito.any(Reader.class)); // Etape 3
} catch (AssertionError e) {
    String feed = "Vous devez utilisez un BufferedReader !";
    fail(feed);
}

De nouveau, grâce aux noms de méthodes, la lecture est assez facile. La ligne de l'étape 1 peut se lire comme "Lorsqu'un nouveau BufferedReader est créer, avec comme argument n'importe quel Reader, alors on retourne un mock object BufferedReader".

La ligne de l'étape 3 va générer une AssertionError si jamais aucun BufferedReader n'est créer. Nous faisons donc un catch sur cette erreur pour savoir si oui ou non l'étudiant à utilisé un BufferedReader.

Générer une erreur

Ce cas apparaît aussi dans la question 7 de la mission 9. Ici, nous voulons vérifier que l'étudiant gère un type d'exception comme demandé. Dans notre cas, nous voulons qu'il retourne une valeur particulière si il rencontre une IOException. Souvenons nous par ailleur que les étudiants doivent utiliser un BufferedReader pour cette question. Pour faire cela, nous allons suivre les étapes suivantes

  1. Créer un mock object sur base d'un BufferedReader
  2. Lorsqu'un nouveau BufferedReader est créer, retourner notre mock object à la place
  3. Lorsqu'une certaine méthode que l'on est certain que l'étudiant va appeler, lancer une IOException
  4. Vérifier si l'excecption remonte jusqu'à notre méthode

Remarquons que cette démarche dépend du fait que nous savons que le code de l'étudiant appelra une certaine méthode. En code Java, nous obtenons

try {
    BufferedReader mockedBR = PowerMockito.mock(BufferedReader); // Etape 1
    PowerMockito.whenNew(BufferedReader.class).withParameterTypes(Reader.class)
                .withArguments(Mockito.any(Reader.class)).thenReturn(mockedBR); // Etape 2
    PowerMockito.doThrow(new IOException("mocking error")).when(mockedBR).readLine(); // Etape 3
    int [] v = Etudiant.readVector("./file1");
    String feedNullValue = "Vous ne renvoyez pas null lorsqu'une IOException se produit !";
    assertThat(feedNullValue,v,nullValue());
} catch (IOException e) { // Etape 4
    String feedNoExceptionCatch = "Vous ne gérez pas les IOException !";
    fail(feedNoExceptionCatch);
}

Vous remarquerez ici que l'on fait deux tests en une fois. D'une part, on test que la valeur de retour est celle attendue (via l'assertion de JUnit). D'autre part, si l'étudiant ne pense pas à faire un catch(IOException e), elle remontera jusqu'à notre méthode et si nous l'attrapons, alors nous savons que l'étudiant ne l'a pas fait.

La ligne intéressante dans cet exemple est celle de l'étape 3 qui peut se lire comme "Lancer une nouvelle IOException lorsque l'on appelle la méthode readLine() de l'objet mockedBR.