Introduction à Mockito
Transcription
Introduction à Mockito
Introduction à Mockito Construction de doublures en Java Contenu 1. Introduction..........................................................................................................................1 2. Création de doublure...........................................................................................................3 3. Stubbing.................................................................................................................................3 4. Vérification............................................................................................................................5 5. Mockito pour Android........................................................................................................8 6. Téléchargement et Installation sous Eclipse.....................................................................8 1. Introduction Un test unitaire teste une classe en isolation. Les effets de bord des autres classes ou du système doivent être éliminés si possible pendant le test. L'atteinte de ce but est généralement rendue difficile par le fait que les classes d'un projet dépendent d'autres classes. L'exemple classique est celui du test d'une classe qui fait appel à une base de données. L'utilisation d'une vraie base de données rend l'écriture des tests très complexe et leur exécution très longue. Certains objets sont très difficiles à instancier, il peut être même impossible de le faire si on est en phase de spécification et que seule l'interface a été écrite. C'est le cas par exemple quand la base de données n'est pas encore disponible. De plus, on ne peut pas savoir si les problèmes sont dus à la classe en cours de test ou aux classes en interaction. C'est là qu'interviennent les doublures. Une doublure est un objet qui simule le comportement d'une classe et garantie que les conditions du test sont toujours les mêmes. Ces objets doublures sont fournis à la classe à tester. Ainsi, la classe à tester peut être isolée des autres classes. Mockito est un générateur automatique de doublures qui peut être utilisé en conjonction avec JUnit. Mockito permet de créer des classes "marionnettes" à partir de n'importe quelle classe ou d'interface. En anglais on appelle ça des "mock objects". Cette classe nous allons pouvoir en contrôler finement le comportement, comme les valeurs de retour de ses méthodes : c'est qu'on appelle le "stubbing". Les objets mocks peuvent être passés à d'autres objets qui sont en test. Les tests peuvent alors valider que la classe réagit correctement durant les tests. Cela permet de s'assurer que l'on teste seulement la classe et qu'il n'y a pas d'effets de bord dûs à d'autres classes. 1.1 Cycle de vie d'un mock dans un cas de test Mockito fonctionne sur le mode de l'espion : 1. Création des mocks ; 2. Description du comportement qu'ils sont censés imiter ; École Nationale Supérieure d'Ingénieurs & Groupe de Laboratoires, C.N.R.S. 6, Boulevard Maréchal Juin, F-14050 CAEN cedex http://www.ensicaen.fr 2 ■ Introduction à Mockito 3. Utilisation des mocks dans le code qui testent le comportement donné ; 4. Interrogation des mocks pour savoir comment ils ont été utilisés durant le test. Dans la suite, nous présentons d'abord un exemple récapitulatif puis détaillons chacune des étapes de la création de doublure. 1.2 Exemple complet Prenons l'exemple de l'interface List de l'API Java que l'on veut doubler pour tester une classe qui manipule cette liste sans se préoccuper de gérer effectivement cette liste. 1.2.1 Création de doublure List mockedList = mock(List.class); Dans cet exemple, nous venons de créer un objet mock à partir de l'interface List. Cet objet implémente l'interface. Par contre son comportement est pour le moment assez limité, un appel à mockedList.get(0) retournera null. La classe à tester se nomme MaClasse et possède un attribut qui est du type List. On suppose que le constructeur permet de prendre le type de liste effectif comme paramètre (sinon on ne peut remplacer la liste en attribut). ENSICAEN - Spécialité Informatique MaClasse c = new MaClasse(mockledList); Pour contrôler le comportement de la doublure que nous avons créée en fonction des appels que l'on veut simuler, nous allons faire du "stubbing". 1.2.2 Le "stubbing" La stubbling consiste à définir le comportement souhaité pour chaque appel des méthodes de l'objet doublé. Supposons que pour tester la classe MaClasse nous appelons la méthode get(0) de la doublure et que la réponse doit « toto ». Dans cas, il faut définir le comportement de cet appel particulier : when(mockedList.get(0)).thenReturn("toto"); Maintenant un appel à mockedList.get(0) retournera la chaîne "toto". Si l'on souhaite répondre "toto" pour n'importe qu'elle indice de la liste : when(mockedList.get(anyInt())).thenReturn("toto"); Maintenant un appel à mockedList.get(indice) avec n'importe quel entier en indice retournera la chaîne "toto". 1.2.3 Vérifier les appels Il peut être intéressant de contrôler qu'une méthode a bien été appelée et avec les bons arguments en paramètre. La ligne suivante permet de s'assurer que la valeur retournée par get(888) est bien "toto". À défaut, une exception est levée. AssertEquals("Problème d'insertion",mockedList.get(888),"tata"); Si l'on souhaite vérifier qu'il y a bien eu un appel à la méthode get avec comme paramètre 888. verify(mockedList).get(888); À défaut, la JVM lève une exception. Détaillons maintenant chaque étape de construction. Introduction à Mockito ■ 3 2. Création de doublure En préambule à la création de d'objetss mock, notons que les importations statiques permettent d'appeler les membres statiques d'une classe sans passer par la classe. Avec la déclaration ci-dessous, il est possible d’appeler la méthode mock() sans passer par mockito.mock(). import static org.mockito.Mockito.*; Il y a deux manières de créer des doublures, avec la méthode mock() et avec l'annotation Java @Mock. 2.1 Création d'une doublure avec la méthode mock() Supposons que l'on crée une double de la classe UneInterface : UneInterface mockSansNom = mock(UneInterface.class); La même création avec un nom rend les messages d'erreurs plus clairs : UneInterface mockAvecNom = mock(UneInterface.class, "ceMock"); 2.2 Création d'une doublure avec l'annotation @Mock @Mock UneInterface ceMock; 2.3 Comportement par défaut Supposons une doublure de la classe Bidon. Bidon monMock = mock(Bidon.class, "bidon"); Dès l'exécution de cet appel, l'objet monMock est créé avec des comportements par défaut, décrit par les assertions suivantes : assertEquals("bidon", monMock.toString()); assertEquals("type numérique : 0 ", 0, monMock.retourneUnEntier()); assertEquals("type booléen : false", false, monMock.retourneUnBooleen()); assertEquals("type collection : vide",0, monMock.retourneUneList().size()); 3. Stubbing Littéralement stub signifie un bouchon. C'est un code minimal ne faisant rien (ou pas grand-chose) mais prêt à être étendu et utile car permettant d'invoquer un logiciel durant son développement. Le stubbling consiste à remplacer ce comportement par défaut des méthodes. 3.1 Stubbing avec la méthode when() Il a deux cas possibles, selon que la méthode possède a un type de retour ou non. 3.1.1 Appel de méthode avec une valeur de retour unique Le stubbing : when(monMock.retourneUnEntier()).thenReturn(3); ENSICAEN - Spécialité Informatique Le nom du mock est automatiquement ceMock. 4 ■ Introduction à Mockito et la description avec JUnit : assertEquals("une première fois 3",3, monMock.retourneUnEntier()); assertEquals("une deuxième fois 3",3, monMock.retourneUnEntier()); Retourne la valeur stubbée autant de fois que nécessaire. 3.1.2 Appel de méthode avec des valeurs de retour consécutives Le stubbing : when(monMock.retourneUnEntier()).thenReturn(3, 4, 5); et la description avec JUnit : assertEquals("une première fois : 3", 3, monMock.retourneUnEntier()); assertEquals("une deuxième fois : 4", 4, monMock.retourneUnEntier()); assertEquals("une troisième fois : 5", 5, monMock.retourneUnEntier()); when(monMock.retourneUnEntier()).thenReturn(3, 4); est un raccourci pour : when(monMock.retourneUnEntier()).thenReturn(3).thenReturn(4); 3.1.3 Apple de méthode avec utilisation de la valeur des paramètres ENSICAEN - Spécialité Informatique Soit la méthode de Bidon : public int retourneUnEntierBis(int i, int j); On pourra écrire : when(monMock.retourneUnEntierBis(4, 2)).thenReturn(4); when(monMock.retourneUnEntierBis(5, 3)).thenReturn(5); et la description avec JUnit : assertEquals("param 4 2: 4", 4, monMock.retourneUnEntierBis(4, 2)); assertEquals("param 5 3: 5", 5, monMock.retourneUnEntierBis(5, 3)); 3.2 Levée d'exception Le fonctionnement est le même. On suppose donnée la méthode : public int retourneUnEntierOuLeveUneExc() throws BidonException; Le stubbing est : when(monMock.retourneUnEntierOuLeveUneExc()).thenReturn(3).thenThrow(new BidonException()); et la description avec Junit est : assertEquals("1er appel: ->3",3, monMock.retourneUnEntierOuLeveUneExc()); try { monMock.retourneUnEntierOuLeveUneExc(); fail(); } catch (BidonException e) { assertTrue("2nd appel -> exception", true); } 3.3 Appel de méthode sans valeur de retour mais avec levée d'exception La méthode de type void est : public void voidEtLeveUneExc() throws BidonException; Introduction à Mockito ■ 5 On écrira : doThrow(new BidonException()).when(monMock).voidEtLeveUneExc(); et la description avec JUnit : try { monMock.voidEtLeveUneExc(); fail(); } catch (BidonException e) { assertTrue("levee exception", true) ; } 3.3.1 Limitations Mockito possède certaines limitations. Premièrement, il ne peut tester les constructions suivantes : ► Les classes déclarées final ; ► Les classes anonymes ; ► Les types primitifs. Deuxièmement, il ne peut pas stubber les méthodes suivantes des objets : ► hashCode() 3.4 Matchers Souvent, on veut spécifier un appel sans que les valeurs des paramètres aient vraiment d’importance. On utilise pour cela des Matchers (import org.mockito.Matchers). when(mockedList.get(anyInt())).thenReturn("element"); verify(mockedList).get(anyInt()); Voici la liste des matchers qui sont très souvent utilisés : ► any(), static <T> T anyObject(). ► anyBoolean(), anyDouble(), anyFloat() , anyInt(), anyString() … ► anyList(), anyMap(), anyCollection(), anyCollectionOf(java.lang.Class<T>clazz), anySet(), <T> java.util.Set<T> anySetOf(java.lang.Class<T> clazz). Attention ! Si on utilise des matchers, tous les arguments doivent être des matchers. La ligne suivante produit une erreur de compilation : verify(mock).someMethod(anyInt(), anyString(), "third argument"); 4. Vérification Mockito garde trace de tous les appels de méthode avec leurs valeurs de paramètre. Vous pouvez utiliser la méthode verify() sur un objet mock pour vérifier qu'une méthode est appelée avec certaines valeurs de paramètres. Ce type de test est quelque fois appelé test de comportement, parce qu'il ne teste pas le résultat de l'appel, mais vérifie qu'une méthode est appelée avec les bons paramètres. @Test public void test1() { ENSICAEN - Spécialité Informatique ► equals(). Mockito vérifie les valeurs en arguments en utilisant equals(). 6 ■ Introduction à Mockito MyClass test = Mockito.mock(MyClass.class); // define return value for method getUniqueId() test.when(test.getUniqueId()).thenReturn(43); // TODO use mock in test.... // now check if method testing was called with the parameter 12 Mockito.verify(test).testing(Matchers.eq(12)); // was the method called twice? Mockito.verify(test, Mockito.times(2)); } 4.1 Fonctionnement de la méthode verify() Elle permet de vérifier : ► quelles méthodes ont été appelées sur un mock ; ► combien de fois ; ► avec quels paramètres ; ► dans que l'ordre. ENSICAEN - Spécialité Informatique Si la vérification échoue, il y a levée d'exception et le test échoue. 4.2 Vérification du nombre d'appels On vérifie que retourneUnBooleen doit avoir été appelée... ► … exactement une fois : verify(monMock).retourneUnBooleen(); verify(monMock, times(1)).retourneUnBooleen(); // équivalent au moins / au plus une fois : verify(monMock, atLeastOnce()).retourneUnBooleen(); verify(monMock, atMost(1)).retourneUnBooleen(); ► … jamais : verify(monMock, never()).retourneUnBooleen(); ► Avec des paramètres : verify(monMock).retourneUnEntierBis(4, 2); 4.3 Vérification de l'ordre des appels import org.mockito.InOrder; Pour vérifier que l'appel (4,2) est effectué avant l'appel (5,3) : InOrder ordre = inOrder(monMock); ordre.verify(monMock).retourneUnEntierBis(4, 2); ordre.verify(monMock).retourneUnEntierBis(5, 3); Marche aussi avec plusieurs mocks : InOrder ordre = inOrder(mock1, mock2); ordre.verify(mock1).foo(); ordre.verify(mock2).yo(); Introduction à Mockito ■ 7 4.4 Et aussi ... Vérification qu'il n'y a pas d'interaction à utiliser à bon escient : verifyNoMoreInteractions(mock); verifyZeroInteractions(mock); La méthode verifyNoMoreInteractions() permet de vérifier que qu'aucune autre méthode n'est appelée. 4.4.1 Espionner les méthodes classiques Il est aussi possible d'espionner un objet classique, qui n'est pas un objet mock. ► Obtenir un objet espionné par la méthode spy : List list = new LinkedList(); List spy = spy(list); ► Appeler des méthodes « normales » sur le spy : spy.add("one"); spy.add("two"); ► Vérifier les appels à la fin : L'annotation @Spy ou la méthode spy() peuvent être utilisées pour espionner un objet concret. Chaque appel, sans spécification contraire, est délégué à l'objet. // Lets mock a LinkedList List list = new LinkedList(); List spy = spy(list); //You have to use doReturn() for stubbing doReturn("foo").when(spy).get(0); // this would not work // real method is called so spy.get(0) // throws IndexOutOfBoundsException (list is still empty) when(spy.get(0)).thenReturn("foo"); Il y a aussi l'annotation @InjectMocks qui tente de faire une injection de dépendance basé sur le type. Par exemple : public class ArticleManagerTest { @Mock private ArticleCalculator calculator; @Mock private ArticleDatabase database; @Spy private UserProvider userProvider = new ConsumerUserProvider(); // creates instance of ArticleManager // and performs constructor injection on it @InjectMocks private ArticleManager manager = new ArticleManager(); @Test public void shouldDoSomething() { MockitoAnnotations.initMocks(this); verify(database).addListener(any(ArticleListener.class)); } } ENSICAEN - Spécialité Informatique verify(spy).add("one"); verify(spy).add("two"); 8 ■ Introduction à Mockito 5. Mockito pour Android 5.1 Créer une application à tester sur Android Créer une application Android qui permet de déclencher une intention avec certains paramètres comme dans l'exemple suivant : public static Intent createQuery(Context context, String query, String value) { Intent i = new Intent(context, MyListActivity.class); i.putExtra("QUERY", query); i.putExtra("VALUE", value); return i; } 5.2 Créer un test Créer un nouveau projet de test pour l'application et une nouvelle classe de test. Écrire un test, en utilisant Mockito pour vérifier que l'intention est déclenchée avec les données supplémentaires correctes. ENSICAEN - Spécialité Informatique Pour cela, construire une doublure de l'objet du contexte avec Mockito comme dans l'exemple suivant : import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import import import import android.content.Context; android.content.Intent; android.os.Bundle; android.test.AndroidTestCase; import fr.ensicaen.cours1i2ac1.mockito.intent.MainActivity; public class MainActivtityTest extends AndroidTestCase { @Mock Context context; @Override protected void setUp() throws Exception { MockitoAnnotations.initMocks(this); } public void testQuery() throws Exception { Intent intent = MainActivity.createQuery(context, "query", "value"); assertNotNull(intent); Bundle extras = intent.getExtras(); assertNotNull(extras); assertEquals("query", extras.getString("QUERY")); assertEquals("value", extras.getString("VALUE")); } } 6. Téléchargement et Installation sous Eclipse Le framework Mockito est hébergé à l'adresse : https://code.google.com/p/mockito/ Introduction à Mockito ■ 9 6.1 Installation 1/ extraire les fichiers de l'archive 2/ Ouvrir un projet Eclipse ou créer un nouveau projet Java. 3/ Ouvrir les propriétés du projet 4/ Sélection « Java Build Path » puis « Libraries » 5/ Cliquer sur « Add external Jars » 6/ Sélectionner tous le fichier jar de Mockito and appuyer sur OK. 6.2 Alternatives Les solutions alternatives à Mockito sont : ► jMock : http://jmock.org/ ► EasyMock : http://easymock.org/ ENSICAEN - Spécialité Informatique 10 ■ Introduction à Mockito Révision Version 0.1 Date Commentaires Basé sur le cours de Mirabelle Nebut, Université de Lille 09 fév 2014 + Lars Vogel (http://www.vogella.com/tutorials/Mockito/article.html) ENSICAEN - Spécialité Informatique