Upload
antoine-rey
View
6.160
Download
5
Embed Size (px)
Citation preview
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’écriture de tests
Zoom sur Mockito
Exemples concrets
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
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)
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)
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)
Documenter le code
Contribuent au design logiciel à la qualité générale de l’application
Objectifs des tests unitaires (2/2)
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
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
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
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
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
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"
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
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
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
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
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());
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());
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)
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()); }}
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); }
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
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
Conclusion Ecrire des tests, cela s’apprend
Tester unitairement, c’est coder
A chaque couche / techno, sa typologie de test