Publicité
Publicité

Contenu connexe

Publicité
Publicité

Tester unitairement une application java

  1. Tester unitairement une application Java 11 février 2016
  2. Objectifs  Positionnement des TU par rapport aux autres catégories de tests  Tour d’horizon des outils facilitant l’écriture de tests  Zoom sur Mockito  Exemples concrets
  3. Sommaire  Les différents types de tests automatisés  Objectifs des tests unitaires  Stratégies de mise en œuvre des tests unitaires  Bonnes pratiques & Difficultés  Décomposition d’un test  L’intérêt des mocks  Boîte à outils : Unitils, Mockito, DbUnit et Spring Test  Exemples de tests de DAO et de contrôleurs Spring MVC  Tester du code legacy
  4.  Tests unitaires  Teste une méthode d’une classe en isolation du reste de l’application  Les tests exécutés lors d’un mvn test  Doivent fonctionner sur un poste de dév déconnecté du réseau  Outils : JUnit, Mockito  Tests d’intégration  Teste un ensemble de composants  Exemples :  Web Service SOAP ou REST  Service métier s’appuyant sur un DAO Hibernate interrogeant une base Oracle  Outils : JUnit, SoapUI Les différents types de tests automatisés (1/2)
  5.  Tests fonctionnels  Exécute des scénarios fonctionnels en simulant des interactions utilisateurs  Outils : Selenium, HP UFT, CasperJS, Cucumber  Tests de performance  Simule la charge utilisateur sur un environnement iso-prod  Outils : JMeter, Gatling, Dynatrace  Tests de vulnérabilité, de robustesse aux pannes, d’installation, de déploiement … Les différents types de tests automatisés (2/2)
  6.  Vérifier le comportement d’une fonctionnalité au regard des exigences  Tester ses développements  Fait partie du job d’un développeur  Sérénité vis-à-vis des tests d’intégration  Se prémunir de régression lors de :  Correction d’anomalies  Evolutions fonctionnelles  Montée de socle technique  Refactoring Objectifs des tests unitaires (1/2)
  7.  Documenter le code  Contribuent  au design logiciel  à la qualité générale de l’application Objectifs des tests unitaires (2/2)
  8.  Tester en priorité :  le code complexe  les corrections de bugs  le code sensible (souvent amené à changer)  Tester les cas limites  Tests en boîte noire / boîte blanche Stratégies de mise en œuvre des tests unitaires
  9.  Valider le fonctionnement par des assertions  Automatiser l’exécution des TU  Un TU doit être rapide à exécuter  Essayer d’avoir une méthode de test par scénario de test  L’échec d’un test unitaire doit être compréhensible  Importance du nom de la méthode de test unitaire Bonnes pratiques
  10.  Jeux de données  Demande une connaissance fonctionnelle  Peut-être complexe et fastidieux à initier  Tester la couche d’accès aux données  Code existant éloigné des principes inspirés de Clean Code Difficultés
  11.  Un test se décompose généralement en 3 étapes import static org.junit.Assert.assertEquals; import org.junit.Test; public class CalculatorTest { @Test public void evaluateAdditionExpression() { Calculator calculator = new Calculator(); Expression exp = new ArithmeticExpression("1+2+3"); int sum = calculator.evaluate(exp); assertEquals(6, sum); } } Décomposition d’un test 3. Then : vérifications du résultat 2. When : appel de la méthode testée 1. Given : initialise l’état du système
  12.  Besoin : tester individuellement un service métier, c’est à dire sans ses adhérences L’intérêt des mocks Service Dao Service Dao Mock Code de production Configuration de test à programmer
  13.  ReflectionAssert.assertReflectionEquals de unitils-core Boîte à outils : assertReflectionEquals de Unitils User user1 = new User(1, "John", "Doe"); User user2 = new User(1, "John", "Connor"); assertReflectionEquals(user1, user2); junit.framework.AssertionFailedError: Expected: User<id=1, firstname="John", lastname="Doe"> Actual: User<id=1, firstname="John", lastname="Connor"> --- Found following differences --- lastname: expected: "Doe", actual: "Connor"
  14. Boîte à outils : Spring Test  Conteneur léger accessible aux tests unitaires et d’intégration  Support de JUnit 4 et TestNG  Chargement du contexte Spring  Injection de dépendances  Mise en cache du contexte Spring  Extensions de JUnit par  Runner : SpringJUnit4ClassRunner  Annotations : @ContextConfiguration, @Rollback, @Sql, @Repeat, @ActiveProfiles …  Listeners : DependencyInjectionTestExecutionListener  Bouchons prêts à l’emploi : MockHttpSession, MockHttpServletRequest …  Classes utilitaires : JdbcTestUtils, AopTestUtils, ReflectionTestUtils, TestTransaction …  Spring MVC Test Framework
  15.  Permet de charger en base des jeux de données  A partir de fichier XML  Favoriser des dataset les plus petits possibles  Suite à un test, permet de vérifier l’état de la base  Comparaison de l’état de la base avec un fichier XML  Ce que DbUnit ne fait pas :  Création de la base et du schéma  Gestion des connexions et des transactions  L’élaboration de jeux de données Boîte à outils : DbUnit
  16.  Créer un mock Boîte à outils : Mockito (1/4) import static org.mockito.Mockito.*; List mockedList = mock(List.class); assertNull(mockedList.get(0)); Les méthodes d’un mock non programmé ne font rien. Elles retournent null ou false .  Programmer un mock LinkedList mockedList = mock(LinkedList.class); when(mockedList.get(0)).thenReturn("first"); assertEquals("first", mockedList.get(0)); assertNull(mockedList.get(1)); Mockito permet de mocker aussi bien des interfaces que des classes. Simule un comportement
  17.  Vérifier les interactions avec le mock Boîte à outils : Mockito (2/4) @Test public void testAdminAuthentication() { UserDao userDao = mock(UserDao.class); UserService userService = new UserService(userDao); User admin = new User("admin"); when(userDao.findOne("admin")).thenReturn(admin); User loadedUser = userService.loadUserByUsername("admin"); verify(userDao).findOne("admin"); } Mock le DAO Programme le DAO Vérifie l’interaction  A utiliser judicieusement  Lorsque la méthode testée ne renvoie pas de résultat  Pour des problématiques de performance
  18.  Partial mocking avec spy  Enregistre les interactions  Simule le comportement de méthodes choisies Boîte à outils : Mockito (3/4) List<String> list = new ArrayList<String>(); List<String> spyList = Mockito.spy(list); spyList.add("one"); assertEquals(1, spyList.size()); Mockito.verify(spyList).add("one"); when(spyList.size()).doReturn(100); assertEquals(100, spyList.size());
  19.  ArgumentCaptor Permet de récupérer la valeur des paramètres d’appel d’un mock Boîte à outils : Mockito (4/4) public class Person { private final String name; public Person(String name) { this.name = name; } public String getName() { return name; } public void inviteToParty(Person friend, Party party) { party.addGuest(friend); } } ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class); Party party = mock(Party.class); Person john = new Person("John"); Person peter = new Person("Peter"); // Peter invites John to the party peter.inviteToParty(john, party); verify(party).addGuest(argument.capture()); // verify John was invited assertEquals("John", argument.getValue().getName());
  20.  Tester unitairement des DAO nécessite une base de données embarquée  Les puristes les considèrent comme des tests d’intégration  Spring Test  Facilite le chargement de la configuration Spring liée à l’infrastructure  Prend en charge la création du schéma  Support des transactions  Laisse la base inchangée après l’exécution du test (débrayable)  Gestion manuelle des transactions  Possibilité d’exécuter du code en dehors d’une transaction Tester des DAO (1/3)
  21. Tester des DAO (2/3)  Avec DbUnit accounts-dataset.xml <?xml version='1.0' encoding='UTF-8'?> <dataset> <ACCOUNT ID="1" BIC="FR7030002005500000157845Z02" LABEL="Account 1"/> <ACCOUNT ID="2" BIC="FR70300023455000021Z4234Y45" LABEL="Account 2"/> </dataset> public class TestHibernateAccountDao extend AbstractDaoTest<HibernateAccountDao> { @Test public void findAccountByIBan() { DbUnitLoader.loadDataset("accounts-dataset.xml"); String iban = "FR70 3000 2005 5000 0015 7845 Z02"; Account account = dao.findAccountByIBan(iban); assertNotNull(account); assertEquals("Account 1", account.getDescription()); } }
  22.  Avec Spring Test Tester des DAO (3/3) Extrait du fichier spring/dao-config.xml <beans profile="test"> <jdbc:embedded-database id="dataSource" type="HSQL"> <jdbc:script location="classpath:create-schema.sql"/> </jdbc:embedded-database> </beans> <beans profile="production"> <jee:jndi-lookup id="dataSource" jndi-name="jdbc/MyDataSource"/> </beans> <bean id="sessionFactory" class="o.s.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref=" dataSource " /> … </bean> <bean id="transactionManager" class="o.s.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <context:component-scan base-package="com.myapp.dao"/> @ContextConfiguration(locations = {"classpath:spring/dao-config.xml"}) @RunWith(SpringJUnit4ClassRunner.class) @ActiveProfiles("test") public class ClinicDaoTests { @Autowired ClinicDao clinicDao; } @Test @Transactional public void shouldInsertOwner() { Collection<Owner> owners = clinicDao.findOwnerByLastName("Schultz"); int found = owners.size(); Owner owner = new Owner(); owner.setFirstName("Sam"); owner.setLastName("Schultz"); owner.setAddress("4, Evans Street"); owner.setCity("Wollongong"); clinicDao.saveOwner(owner); assertThat(owner.getId().longValue()).isNotEqualTo(0); owners = clinicDao.findOwnerByLastName("Schultz"); assertThat(owners.size()).isEqualTo(found + 1); }
  23.  Avec Spring Test @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({"classpath:spring/mvc-core-config.xml"}) @WebAppConfiguration public class PetControllerTests { @Autowired PetController petController; MockMvc mockMvc; @Before public void setup() { mockMvc = MockMvcBuilders.standaloneSetup(petController) .build(); } @Test public void testProcessUpdateFormSuccess() throws Exception { mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", 1,1) .param("name", "Betty") .param("type", "hamster") .param("birthDate", "2015/02/12") ) .andExpect(status().is3xxRedirection()) .andExpect(view().name("redirect:/owners/{ownerId}")); } } Tester un contrôleur Spring MVC
  24. Tester du code legacy  Code faisant appel à un Singleton avec getIntance()  Création d’un setter de visibilité package permettant de passer un mock  Méthode phagocyte  Refactoring en sous méthodes qui seront testées individuellement  Méthode private  Changement de la visibilité en portée package
  25. Conclusion  Ecrire des tests, cela s’apprend  Tester unitairement, c’est coder  A chaque couche / techno, sa typologie de test

Notes de l'éditeur

  1. Classement personnel issu d’un certain pragmatisme Test de la configuration Spring s’apparente davantage un test d’intégration mais comme stable et relativement rapide => TU ? Utilisation d’une base de données en mémoire ?
  2. Tests fonctionnels  tests d’acceptance / tests d’IHM HP Fortify permet de tester la vulnérabilité du code à l’égard de failles de sécurité Ces 4 types de tests sont complémentaires. Un TU garantie que le contrat de la classe à tester est respectée mais ne garantie pas que l’application respecte les spécifications fonctionnelles. De part leur nature, les tests d’intégration et les tests fonctionnels sont moins stables : dépendances aux données, à la disponibilité des adhérences, à l’infra …
  3. Objectif premier lorsqu’on ne pratique pas le TDD : trouver des bugs Le TU fait partie du Done en agile Les TU permettent au CP de s’assurer à minima que le développeur à tester son code Se prémunir de régression : penser aux autres développeurs qui maintiendront l’application Un harnais de tests est toujours sécurisant
  4. Documenter le code : Spring REST Docs utilise les tests pour générer la doc de son API web. Contrairement à la JavaDoc, les TU sont toujours à jour. Contribue au design de l’application : un code testable est souvent plus lisible / maintenable (pas de singleton ou de méthodes phagocytes) Design logiciel : couplage faible (IoC), principe de substitution de Liskow (emploi correct de l’héritage), conception orientée service
  5. L’écriture de TU a un coût (souvent estimé à 20% de la charge de dévs) et sa maintenance en a également un, Eviter de tester du code legacy qui ne change jamais
  6. Des TU sans assertions ne servent à rien (mise à part vérifier qu’aucune exception n’est levée) Un TU non exécuté (@Ignore ou EclipseTestXXX) n’est pas maintenu Nom des méthodes de tests : nom à rallonge en camelCase ou avec des _ Messages d’erreur davantage explicite avec AssertJ que JUnit
  7. Qui dit test, dit jeux de données. Code existant difficilement testable ne respectant pas les principes OCP, LSP, KISS, ISP, DIP, SOLID (cf Clean Code)
  8. Exemple inspiré de la documentation Junit. Mais décomposition valable quelque soit la techno. Given When Then fait partie de la méthode agile Behavior-Driven Development http://martinfowler.com/bliki/GivenWhenThen.html Bonne pratique : séparer les blocs given/when/then par une ligne vide
  9. Mock = simulacre Les bouchons ne sont pas des simulacres : http://martinfowler.com/articles/mocksArentStubs.html Les mockes évitent de devoir coder des stubs et les maintenir (par exemple lors d’ajout de méthode dans une interface) Adhérences : DAO, autre service métier … Utilisation des mocks contestées Le NoMock Movement : http://antoniogoncalves.org/2012/11/27/launching-the-nomock-movement/ Mock ou pas Mock ? : https://www.fierdecoder.fr/2015/11/mock-ou-pas-mock/
  10. Autres outils non présentés : AssertJ, assertReflectionEquals pratique dans les cas suivant : Mapping objet / objet Marshalling / Unmarshalling Persistance / rechargement en base
  11. Introduction : Spring encourage le développement en utilisant des POJO. En principe, ceux-ci sont testables sans Spring. Cependant, il peut être tout de même intéressant de s’appuyer sur Spring pour : Profiter de l’injection de dépendance dans les tests, Tester unitairement la configuration Spring Réutiliser la configuration Spring Possibilité de ne pas utiliser le module spring test : création du contexte applicatif dans la méthode setUp Spring 4.2 supporte Junit 4.9 et + Mise en cache : pour tous les tests utilisant le même fichier de configuration Spring Demander à JUnit d’utiliser tel runner passe par l’annotation @RunWith (même principe utilisé par Unitils ou Mockito) Autres annotations : BeforeTransaction, AfterTransaction DirtiesContext : invalide le contexte afin que la prochaine méthode de test reconstruise le context Spring IfProfileValue : exécute un test en fonction de l’évaluation d’une variable système (ex: test spécifique à la plateforme) Autres bouchons : MockJspWriter, MockServletContext, MockPortletSession Spring MVC Test Framework : http://docs.spring.io/spring/docs/current/spring-framework-reference/html/integration-testing.html#spring-mvc-test-framework Les annotations @Sql, @SqlConfig et @SqlGroup ont été introduites dans Spring 4.1
  12. Alternative : DbSetup pour créer des jeux de données en Java
  13. Exemples issus de la documentation officielle : http://mockito.org/
  14. A la place du verify, privilégier plutôt assertEquals(admin, loadedUser); Performance : vérifier le nombre d’appel d’un WS
  15. Plus d’informations : http://www.baeldung.com/mockito-spy
  16. Exemple tiré du blog https://rwehner.wordpress.com/2010/02/23/mockito-some-useful-programming-examples/ D’autres exemples : http://www.programcreek.com/java-api-examples/org.mockito.ArgumentCaptor
  17. Base de données embarques : H2 ou HSQLDB DbUnit (XML) ou DbSetup (Java) Création du schéma : ResourceDatabasePopulator, @Sql, namespace <jdbc:initialize-database/> Gestion manuelle des transactions : TestTransaction Exécution du code en dehors des tx : @BeforeTransaction, @AfterTransaction
  18. Nécessite une surcouche technique. Exemple : Spring Test DbUnit https://github.com/springtestdbunit/spring-test-dbunit Dans cet exemple, la classe abstraite AbstractDaoTest est chargée de : Instancier le DAO à tester Initialiser la base de données embarquée Charger la configuration Hibernate Créer la table ACCOUNT La classe AbstractDaoTest utilise ou non Spring Test La classe DbUnitLoader factorise le code permettant de charger un dataset
  19. Classe de test inspiré de la classe ClinicServiceJpaTests du projet spring-petclinic La méthode assertThat vient de la librairie AssertJ http://joel-costigliola.github.io/assertj/ @Transactional : inutile de nettoyer la base après le test
  20. Extrait de spring-petclinic : https://github.com/spring-projects/spring-petclinic/blob/master/src/test/java/org/springframework/samples/petclinic/web/PetControllerTests.java
Publicité