Module Intgration Continue Mettre en place des Tests

  • Slides: 40
Download presentation
Module Intégration Continue Mettre en place des Tests Unitaires

Module Intégration Continue Mettre en place des Tests Unitaires

Différents types de tests Test d’acceptation Le système répond-il aux besoins de l’utilisateur finale

Différents types de tests Test d’acceptation Le système répond-il aux besoins de l’utilisateur finale Test d’intégration Vérifier le fonctionnement avec les services tiers Test unitaire Vérifier le fonctionnement seul

Unit Test / Test unitaire • « Unit » représente une méthode ou une

Unit Test / Test unitaire • « Unit » représente une méthode ou une fonction • « Unit Test » : partie de code invoquant une autre partie de code afin de valider son fonctionnement

Caractéristiques • • Il doit être automatisé et rejouable Il doit facile à implémenter

Caractéristiques • • Il doit être automatisé et rejouable Il doit facile à implémenter et compréhensible Il reste tout le long du projet (sauf réécriture de la fonctionnalité) Tout le monde peut l’exécuter Il ne doit pas dépendre de services tiers Son temps d’exécution doit être court Limiter le risque de régression Doit être isolé. Un test ne doit pas impacté un autre test.

Un test unitaire, c’est

Un test unitaire, c’est

3 voir 5 étapes • Initialisation • Préparation des données • Appel de la

3 voir 5 étapes • Initialisation • Préparation des données • Appel de la méthode • Vérification des données en sortie • Démontage / libération des ressources Parties toujours présentes

Rapide • Nécessaire au vue de leur nombre § En moyenne, entre 100 et

Rapide • Nécessaire au vue de leur nombre § En moyenne, entre 100 et 200 tests unitaires pour un microservice • Une exécution plusieurs fois par jour dans le cas d’une intégration continue

Cohérence • Données en entrée doivent être invariantes § § § Pour les dates,

Cohérence • Données en entrée doivent être invariantes § § § Pour les dates, on s’assura qu’il s’agira toujours de la même date. On évitera d’utiliser la date du jour Eviter d’utiliser des nombres aléatoires Eviter de dépendre de l’OS Si aucune modification du code de l’application, il faut garantir que le test retournera toujours le même état

Atomicité • Seulement 2 états possibles PASS FAIL

Atomicité • Seulement 2 états possibles PASS FAIL

Une seule responsabilité • Un test ne vérifie qu’un seul scénario § Mais peut

Une seule responsabilité • Un test ne vérifie qu’un seul scénario § Mais peut contenir plusieurs assertions • Il ne doit appeler qu’une seule méthode à tester • En lisant le nom de la méthode de test, on doit comprendre la fonctionnalité testée

Isolation • Pas de dépendance entre les tests • Qu’importe l’ordre d’exécution des tests,

Isolation • Pas de dépendance entre les tests • Qu’importe l’ordre d’exécution des tests, le résultat doit être toujours le même. • Pas de partage de données entre les tests. Un test ne doit pas préparer des données pour un autre test. • Un test ne doit pas altérer les données d’entrée des autres tests

Environnement Isolation • Ne doit pas dépendre de services tiers Web. Service API Test

Environnement Isolation • Ne doit pas dépendre de services tiers Web. Service API Test unitaire Méthode à tester Solution : « mocker » / bouchonné avec l’injection de dépendance SGBD LDAP Time

Environnement Isolation • Des solutions : § § § Mockito DBUnit avec H 2

Environnement Isolation • Des solutions : § § § Mockito DBUnit avec H 2 Wire. Mock

Classe Isolation • Une méthode testée ne doit pas dépendre d’autres classes § Eviter

Classe Isolation • Une méthode testée ne doit pas dépendre d’autres classes § Eviter que le test échoue à cause de l’appel à une autre classe. Solution : « mocker » / bouchonné avec l’injection de dépendance

Auto descriptible • Le nom de la méthode de test doit permettre de comprendre

Auto descriptible • Le nom de la méthode de test doit permettre de comprendre le test § Pas besoin de le documenter • Le code contenu dans le test doit être facilement lisible et compréhensible • Représente § § la « documentation » de l’application Les spécifications de l’application

Conseils • Ne pas mettre de conditions, boucles au sein d’un test • Utiliser

Conseils • Ne pas mettre de conditions, boucles au sein d’un test • Utiliser des assertions pour tester les donnés en sortie § Assertj / hamcrest / Assert • Mettre des messages claires dans les assertions afin de faciliter la compréhension • Ne pas livrer les classes de test production § Les classes de test ne doit pas être présentes dans les jar • Ne pas créer de méthodes spécifiques pour les tests • Les méthodes de tests unitaires ne doivent pas contenir trop de lignes de code

Faire des tests unitaires avec j. Unit

Faire des tests unitaires avec j. Unit

j. Unit • Framework opensource pour : § § le développement de tests unitaires

j. Unit • Framework opensource pour : § § le développement de tests unitaires l’exècution des tests automatisés • Créé par Kent Beck et Erich Gamma • Fait partie de la série x. Unit (PHPUnit, Nunit, CUnit…) • j. Unit 4 et JUnit 5 sont les 2 versions les plus utilisées

Ecrire un test avant j. Unit 4 Etendre la classe Test. Case Suffixer par

Ecrire un test avant j. Unit 4 Etendre la classe Test. Case Suffixer par Test Optionnel Préparer les données dans la méthode set. Up Définir les tests dans les méthodes préfixées par test. XXX Optionnel Libérer les ressources dans la méthode tear. Down public class Math. Utils. Test extends Test. Case { @Override protected void set. Up() throws Exception { System. out. println("Pré-initialisation du test"); } public void test. Factoriel. Zero() { System. out. println("Verification factorielle 0"); assert. Equals(1 L, Math. Utils. factoriel(0 L)); } public void test. Factoriel. Ten() { System. out. println("Verification factorielle 100"); assert. Equals(3_628_800 L, Math. Utils. factoriel(10 L)); } @Override protected void tear. Down() throws Exception { System. out. println("Demontage du test"); } }

Ecrire un test avec j. Unit 4 public class Math. Utils. Test { Suffixer

Ecrire un test avec j. Unit 4 public class Math. Utils. Test { Suffixer par Test Pour chaque méthode de la classe Optionnel Initialiser la classe avec @Before. Class Optionnel Initialiser les données avec la méthode de test @Before Définir la méthode de tests en annotant avec @Test Optionnel Libérer les ressources avec @After Optionnel Libérer les ressources de la classe avec @After. Class @Before. Class public static void init. Class() throws Exception { System. out. println("Initialisation de la classe"); } @Before public void set. Up() throws Exception { System. out. println("Pré-initialisation du test"); } @Test public void should_return_one_when_number_is_zero() { System. out. println("Verification factorielle 0"); Assert. assert. Equals(1 L, Math. Utils. factoriel(0 L)); } @Test public void should_return_big_number_when_number_is_one_hundred() { System. out. println("Verification factorielle 100"); Assert. assert. Equals(3_628_800 L, Math. Utils. factoriel(10 L)); } @After public void tear. Down() throws Exception { System. out. println("Demontage du test"); } @After. Class public static void tear. Down. Class() throws Exception { System. out. println("Demontage de la classe de test"); } }

Ecrire un test avec j. Unit 4 Suffixer par Test Pour chaque méthode de

Ecrire un test avec j. Unit 4 Suffixer par Test Pour chaque méthode de la classe Optionnel Initialiser la classe avec @Before. Class Optionnel Initialiser les données avec la méthode de test @Before Définir la méthode de tests en annotant avec @Test Optionnel Libérer les ressources avec @After Optionnel Libérer les ressources de la classe avec @After. Class

Depuis j. Unit 5 Suffixer par Test Pour chaque méthode de la classe Optionnel

Depuis j. Unit 5 Suffixer par Test Pour chaque méthode de la classe Optionnel Initialiser la classe avec @Before. All Optionnel Initialiser les données avec la méthode de test @Before. Each Définir la méthode de tests en annotant avec @Test Optionnel Libérer les ressources avec @After. Each Optionnel Libérer les ressources de la classe avec @After. All • Réécrit en Java 8 • Renommage et éclaircissement des annotations • Eclatement du projet en plusieurs jar avec possibilité d’intégrer les anciennes versions de j. Unit • Ajout de nouvelles assertions • Nouvelle annotation @ Parameterized. Test

Utilisation des assertions • Méthodes permettant : § § § valider les données des

Utilisation des assertions • Méthodes permettant : § § § valider les données des méthodes testées la forme « assert. XXX » Présente dans la classe « org. junit. Assert » • Le plus souvent 2 paramétres : § § 1 er : valeur attendue « expected » La valeur en retour de la méthode testée « actual »

Assertion Méthode Description assert. Equals / assert. Not. Equals Permet de valider que 2

Assertion Méthode Description assert. Equals / assert. Not. Equals Permet de valider que 2 valeurs sont ou ne sont pas égales assert. False() / assert. True() Vérifier que la valeur est false ou true assert. Null / assert. Not. Null Vérifier que l’objet est null ou non null. assert. Same() Verifier que 2 objets référencent le même espace mémoire ( obj 1 == obj 2) assert. Array. Equals Permet de valider que les éléments dans les tableaux sont égaux.

Tester les exceptions Avant j. Unit 4 • Utilisation de la méthode fail() public

Tester les exceptions Avant j. Unit 4 • Utilisation de la méthode fail() public void test. Negative. Number() { try{ Math. Utils. factoriel(-12); fail(); // On ne doit passer ici }catch(Illegal. Argument. Exception e) { } } j. Unit 4+ • Utilisation du paramétre expected de l’annotation @Test(expected = Illegal. Argument. Exception. class) public void should_throw_exception_when_number_is_negative() { Math. Utils. factoriel(-12); }

Tester les exceptions j. Unit 5. (depuis junit 4. 7) • Utilisation de @Rule

Tester les exceptions j. Unit 5. (depuis junit 4. 7) • Utilisation de @Rule et Expected. Exception • Permet de comparer plus finement l’exception remontée. Par exemple, contrôler le contenu du message @Rule public Expected. Exception expected. Exception = Expected. Exception. none(); @Test public void should_throw_exception_when_number_is_negative() { expected. Exception. expect(Illegal. Argument. Exception. class); expected. Exception. expect. Message(Core. Matchers. equal. To("Le nombre doit être positif")); Math. Utils. factoriel(-12); }

Comment lancer les TU ? • Avec maven § « mvn test » •

Comment lancer les TU ? • Avec maven § « mvn test » • Avec Eclipse : § § Sélectionner le projet Clique droit puis Run As > Junit Test

Mocker des services

Mocker des services

Concepts • Isoler les méthodes testées des services externes • Limiter la dépendances aux

Concepts • Isoler les méthodes testées des services externes • Limiter la dépendances aux classes • Vérifier le comportement interne de la méthode testée. Nombre de fois que la méthode appelle un service, vérifier les éléments passés en paramètres des services appelés Solution : Mockito

Mockito • Solution Open. Source « https: //site. mockito. org/ » • Modèle «

Mockito • Solution Open. Source « https: //site. mockito. org/ » • Modèle « Set-Run-Verify » § § § Préparation des mocks Exécution du test Vérification des données transférés dans les mocks • Permet de retourner des valeurs et de lever des exceptions • Faciliter de mise en œuvre § Plus besoin de créer des classes de mock spécifiques pour les tests

Principe Classe B « Web. Service » Classe A « A tester » Classe

Principe Classe B « Web. Service » Classe A « A tester » Classe C « Autre Service »

Principe Instancie et injecte les classes « mockées » Classe Test Unitaire Mockito Chaque

Principe Instancie et injecte les classes « mockées » Classe Test Unitaire Mockito Chaque test décrit les données restituées lors des appels aux « mocks » Chaque test peut vérifier les données transmises aux « mocks » Classe Mock B « Web. Service » Classe A « A tester » Classe Mock C « Autre Service »

Mise en place Mockito • Ajout de la dépendance maven <dependency> <group. Id>org. mockito</group.

Mise en place Mockito • Ajout de la dépendance maven <dependency> <group. Id>org. mockito</group. Id> <artifact. Id>mockito-core</artifact. Id> <version>2. 27. 0</version> <scope>test</scope> </dependency> Scope « test » Mockito n’est utilisé uniquement pour les tests

Mise en place Mockito • Importer Mockito : import static org. mockito. Mockito. *;

Mise en place Mockito • Importer Mockito : import static org. mockito. Mockito. *; • Ajout de l’annotation @Run. With sur la classe de test 4 nit Ju @Run. With(Mockito. JUnit. Runner. class) public class User. Service. Test { /*. . . */ } 5 t i n Ju @Extend. With(Mockito. Extension. class) @Run. With(JUnit. Platform. class) public class User. Service. Test { • Déclarer les services « mockés » avec @Mock private User. Jdbc. Dao user. Jdbc. Dao;

Mise en place Mockito • Setter la classe mockée @Before public void set. Up()

Mise en place Mockito • Setter la classe mockée @Before public void set. Up() { this. user. Service = new User. Service(); this. user. Service. set. User. Jdbc. Dao(this. user. Jdbc. Dao); } • Réaliser un test en initialisant les données du mock @Test public void shoud_return_all_users() { User u 1 = new User(1 L, "Daenerys", "Targaryen", "061111"); Mockito. when(this. user. Jdbc. Dao. find. All()). then. Return(Arrays. as. List(u 1)); Collection<User> ret = this. user. Service. find. All(); Assert. assert. False(ret. is. Empty()); Assert. assert. Array. Equals(new User[] {u 1}, ret. to. Array()); }

Contrôler les réponses des mocks • Possibilité de contrôler les réponses des mocks avec

Contrôler les réponses des mocks • Possibilité de contrôler les réponses des mocks avec § § la classe « Argument. Matcher » (eq, any, contains…) Avec la librairie « hamcrest » @Test public void should_find_user_when_user_exists() { User u 1 = new User(1 L, "Daenerys", "Targaryen", "061111"); User u 2 = new User(2 L, "Jon", "Snow", "071111"); when(this. user. Jdbc. Dao. find. By. Id(eq(1 L))). then. Return(u 1); when(this. user. Jdbc. Dao. find. By. Id(eq(2 L))). then. Return(u 2); User r 1 = this. user. Service. find. By. Id(1 L). or. Else. Throw(() -> fail("Utilisateur non trouvé")); Assert. assert. Equals((Long)1 L, r 1. get. Identifier()); Assert. assert. Equals("Daenerys", r 1. get. First. Name()); User r 2 = this. user. Service. find. By. Id(2 L). or. Else. Throw(() -> fail("Utilisateur non trouvé")); Assert. assert. Equals((Long)2 L, r 2. get. Identifier()); Assert. assert. Equals("Jon", r 2. get. First. Name()); } 2 réponses possibles en fonction de l’identifiant

Vérifier les appels • Vérifier les paramètres passés au méthode verify(this. user. Jdbc. Dao).

Vérifier les appels • Vérifier les paramètres passés au méthode verify(this. user. Jdbc. Dao). add. User(eq(u 1)); • Vérifier le nombre de fois qu’une méthode est appelée verify(this. user. Jdbc. Dao, at. Least. Once()). add. User(eq(u 1)); verify(this. user. Jdbc. Dao, times(3)). add. User(eq(u 1)); § Et les variantes : never(), at. Most()

Capturer les paramètres internes • « Argument. Captor » permet d’intercepter les objets créés

Capturer les paramètres internes • « Argument. Captor » permet d’intercepter les objets créés en interne et utilisés pour appeler des méthodes « mockées » @Test public void should_find_user_by_first_name() { //Appel du service this. user. Service. find. User. By("Daenerys", null); //Capture du dao appelé Argument. Captor<User. Criteria> captor = Argument. Captor. for. Class(User. Criteria. class); verify(this. user. Jdbc. Dao). find. By. Criteria(captor. capture()); //Vérification du passage de criteres User. Criteria user. Criteria = captor. get. Value(); //Vérification des données Assert. assert. Null(user. Criteria. get. Name()); Assert. assert. Equals("Daenerys", user. Criteria. get. First. Name()); }

Mocker partiellement un objet • « spy » : permet de mocker partiellement un

Mocker partiellement un objet • « spy » : permet de mocker partiellement un objet @Test public void should_modify_array_size() { List<User> users = new Array. List<>(); users. add(new User(1 L, "Daenerys", "Targaryen", "061111")); users. add(new User(2 L, "Jon", "Snow", "071111")); assert. Equals(2, users. size()); List<User> spy = spy(users); when(spy. size()). then. Return(10); assert. Equals(10, spy. size()); User r 1 = spy. get(0); assert. Equals(r 1. get. First. Name(), "Daenerys"); }

Limitation • Mockito ne sait pas « mocker » des méthodes statiques Solution :

Limitation • Mockito ne sait pas « mocker » des méthodes statiques Solution : Power. Mock