Tester unitairement
une application Java
11 février 2016
Objectifs
 Positionnement des TU par rapport aux autres catégories de tests
 Tour d’horizon des outils facilitant l’écri...
Sommaire
 Les différents types de tests automatisés
 Objectifs des tests unitaires
 Stratégies de mise en œuvre des tes...
 Tests unitaires
 Teste une méthode d’une classe en isolation du reste de l’application
 Les tests exécutés lors d’un m...
 Tests fonctionnels
 Exécute des scénarios fonctionnels en simulant des interactions utilisateurs
 Outils : Selenium, H...
 Vérifier le comportement d’une fonctionnalité au
regard des exigences
 Tester ses développements
 Fait partie du job d...
 Documenter le code
 Contribuent
 au design logiciel
 à la qualité générale de l’application
Objectifs des tests unita...
 Tester en priorité :
 le code complexe
 les corrections de bugs
 le code sensible (souvent amené à changer)
 Tester ...
 Valider le fonctionnement par des assertions
 Automatiser l’exécution des TU
 Un TU doit être rapide à exécuter
 Essa...
 Jeux de données
 Demande une connaissance fonctionnelle
 Peut-être complexe et fastidieux à initier
 Tester la couche...
 Un test se décompose généralement en 3 étapes
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public...
 Besoin : tester individuellement un service métier,
c’est à dire sans ses adhérences
L’intérêt des mocks
Service
Dao
Ser...
 ReflectionAssert.assertReflectionEquals de unitils-core
Boîte à outils : assertReflectionEquals de Unitils
User user1 = ...
Boîte à outils : Spring Test
 Conteneur léger accessible aux tests unitaires et d’intégration
 Support de JUnit 4 et Tes...
 Permet de charger en base des jeux de données
 A partir de fichier XML
 Favoriser des dataset les plus petits possible...
 Créer un mock
Boîte à outils : Mockito (1/4)
import static org.mockito.Mockito.*;
List mockedList = mock(List.class);
as...
 Vérifier les interactions avec le mock
Boîte à outils : Mockito (2/4)
@Test
public void testAdminAuthentication() {
User...
 Partial mocking avec spy
 Enregistre les interactions
 Simule le comportement de méthodes choisies
Boîte à outils : Mo...
 ArgumentCaptor
Permet de récupérer la valeur des
paramètres d’appel d’un mock
Boîte à outils : Mockito (4/4)
public clas...
 Tester unitairement des DAO nécessite une base de données embarquée
 Les puristes les considèrent comme des tests d’int...
Tester des DAO (2/3)
 Avec DbUnit
accounts-dataset.xml
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<ACCOUNT ID="1" B...
 Avec Spring Test
Tester des DAO (3/3)
Extrait du fichier spring/dao-config.xml
<beans profile="test">
<jdbc:embedded-dat...
 Avec Spring Test
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring/mvc-core-config.xml"})...
Tester du code legacy
 Code faisant appel à un Singleton avec getIntance()
 Création d’un setter de visibilité package p...
Conclusion
 Ecrire des tests, cela s’apprend
 Tester unitairement, c’est coder
 A chaque couche / techno, sa typologie ...
Prochain SlideShare
Chargement dans…5
×

Tester unitairement une application java

5 705 vues

Publié le

Présente les différents types de tests automatisés, les objectifs des tests unitaires, les stratégies de mise en œuvre, les bonnes pratiques, les difficultés, ce qu'est un mock, différents outils (Unitils, Mockito, DbUnit, Spring Test) et des exemples de tests (DAO et contrôleurs Spring MVC), sans oublier le test de code legacy.

Publié dans : Technologie
0 commentaire
6 j’aime
Statistiques
Remarques
  • Soyez le premier à commenter

Aucun téléchargement
Vues
Nombre de vues
5 705
Sur SlideShare
0
Issues des intégrations
0
Intégrations
4 913
Actions
Partages
0
Téléchargements
44
Commentaires
0
J’aime
6
Intégrations 0
Aucune incorporation

Aucune remarque pour cette diapositive
  • 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 ?
  • 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 …
  • 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
  • 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
  • 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
  • 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
  • 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)
  • 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
  • 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/
  • Autres outils non présentés : AssertJ,
    assertReflectionEquals pratique dans les cas suivant :
    Mapping objet / objet
    Marshalling / Unmarshalling
    Persistance / rechargement en base
  • 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
  • Alternative : DbSetup pour créer des jeux de données en Java
  • Exemples issus de la documentation officielle : http://mockito.org/
  • A la place du verify, privilégier plutôt assertEquals(admin, loadedUser);
    Performance : vérifier le nombre d’appel d’un WS
  • Plus d’informations : http://www.baeldung.com/mockito-spy
  • 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
  • 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

  • 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
  • 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
  • Extrait de spring-petclinic : https://github.com/spring-projects/spring-petclinic/blob/master/src/test/java/org/springframework/samples/petclinic/web/PetControllerTests.java
  • Tester unitairement une application java

    1. 1. Tester unitairement une application Java 11 février 2016
    2. 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. 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. 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. 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. 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. 7.  Documenter le code  Contribuent  au design logiciel  à la qualité générale de l’application Objectifs des tests unitaires (2/2)
    8. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 25. Conclusion  Ecrire des tests, cela s’apprend  Tester unitairement, c’est coder  A chaque couche / techno, sa typologie de test

    ×