Tester unitairement une application java

25
Tester unitairement une application Java 11 février 2016

Transcript of Tester unitairement une application java

Page 1: Tester unitairement une application java

Tester unitairement une application Java

11 février 2016

Page 2: Tester unitairement une application java

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

Page 3: Tester unitairement une application java

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

Page 4: Tester unitairement une application java

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)

Page 5: Tester unitairement une application java

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)

Page 6: Tester unitairement une application java

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)

Page 7: Tester unitairement une application java

Documenter le code

Contribuent au design logiciel à la qualité générale de l’application

Objectifs des tests unitaires (2/2)

Page 8: Tester unitairement une application java

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

Page 9: Tester unitairement une application java

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

Page 10: Tester unitairement une application java

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

Page 11: Tester unitairement une application java

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

Page 12: Tester unitairement une application java

Besoin : tester individuellement un service métier, c’est à dire sans ses adhérences

L’intérêt des mocks

Service

Dao

Service

DaoMock

Code de production Configuration de test

à programmer

Page 13: Tester unitairement une application java

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"

Page 14: Tester unitairement une application java

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

Page 15: Tester unitairement une application java

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

Page 16: Tester unitairement une application java

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 mockLinkedList 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

Page 17: Tester unitairement une application java

Vérifier les interactions avec le mock

Boîte à outils : Mockito (2/4)

@Testpublic 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

Page 18: Tester unitairement une application java

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());

Page 19: Tester unitairement une application java

ArgumentCaptorPermet 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 partypeter.inviteToParty(john, party);

verify(party).addGuest(argument.capture());// verify John was invitedassertEquals("John", argument.getValue().getName());

Page 20: Tester unitairement une application java

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)

Page 21: Tester unitairement une application java

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()); }}

Page 22: Tester unitairement une application java

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); }

Page 23: Tester unitairement une application java

Avec Spring Test@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration({"classpath:spring/mvc-core-config.xml"})@WebAppConfigurationpublic 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

Page 24: Tester unitairement une application java

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

Page 25: Tester unitairement une application java

Conclusion Ecrire des tests, cela s’apprend

Tester unitairement, c’est coder

A chaque couche / techno, sa typologie de test