TESTS UNITAIRES JAVA

NOTIONS :

Ils sont où les tests ?

Source : Planche « Where are the test? », CommitStrip.com, février 2017.

L’objectif est de tester une « unité » du logiciel, à savoir une méthode, un groupe de méthode ou une classe.
Chaque type de test répond à un besoin particulier et c’est l’ensemble de ces tests qui permet d’avoir un logiciel de qualité.
Un test unitaire ne fait appel à d’autres ressources que la classe testée (≠ test d’intégration qui utilise une base de données ou des sockets).
Il faut séparer le code de la classe, du code qui permet de la tester. Dans chaque classe de test, il y aura une méthode par méthode à tester. Dans chaque classe du logiciel, il y aura une classe sœur dédiée au test unitaire.
Une fois que le test unitaire existe, il valide la fonction auquel il est associé, à un moment « t » de la vie du projet.

Il évite ainsi :

  • que la fonction soit inutilisée ou trop complexe (plus susceptible de défaillir) ;
  • les régressions (la fonction perdure s’il y a une évolution). On soulage ainsi les équipes de QA (Assurance Qualité).

Se poser les questions suivantes :

  • Une méthode fait-elle bien ce que vous voulez qu’elle fasse ?
  • Est-ce que je peux faire le test qui va avec chaque fonction ?
  • Dois-je concevoir mon test avant d’écrire ma fonction ?
  • Quel est le coût pour l’écriture du test, pour l’écriture du code et pour la dette technique ?
  • Quels sont vos objectifs de performance et de maintenabilité ?
  • Quelle est votre tolérance aux pannes ?

1 (1)

1 (3)

Mise en pratique grâce à un framework de test unitaire, dans notre cas JUnit (import org.junit.Assert et org.junit.Test).
JUnit fournit des méthodes pour tester que certaines conditions sont satisfaites. Il crée une instance de la classe, appelé avec la méthode @Test. Il compare le résultat attendu avec le résultat obtenu. Si échec, une exception est levée.

TDD (Test Driven Development) : Technique de développement où l’écriture du test précédent l’écriture du code. Le code est alors plus proche du besoin fonctionnel et documenter en permanence.

BDD (Behavior Driven Development) : Approche de développement basé sur la notion de comportement de l’application. Faciliter la compréhension du besoin du client.

Le réfactoring : Consiste à améliorer le code existant sans modifier son comportement.

Tests boite noire (black-box) : Les tests se font sans que le testeur ne connaisse le contenu de la méthode qu’il va tester.

Tests boite blanche (white-box) : Les tests donne accès au contenu de la méthode à tester.

La couverture de code : Mesure qui permet d’identifier la proportion du code testé. On distingue la couverture linéaire (proportion de lignes de code exercées lors du test) de la couverture conditionnelle calcule la proportion des chemins logiques couverts.

INTÉGRATION DE JUNIT AVEC POWERMOCK ET MOCKITO :
Pour tester en isolation (test unitaire), nous allons avoir besoin d’une librairie de mock, qui écrira de fausses implémentations de toutes les dépendances de notre objet.

Intégration de Powermock

Source : Bertrand Minisclou

Notes : Utiliser les mocks pour les données / les stubs pour les interfaces. Les stubs ne peuvent jamais faire échouer les tests.

ARCHITECTURE D’UN TEST UNITAIRE :

1 (2)

Voici un squelette de test :
① Instancier la classe à tester T (New=> Junit Test Case, la règle de nommage de la classe de test : NomClassTest) ;
② Initialiser T ;
③ Générer les arguments pour la méthode à tester ;
④ Générer le résultat (run as et JUnit test) ;
⑤ Tester la méthode avec les arguments ;
⑥ Vérifier le résultat ;
⑦ Recommence depuis 3 tant qu’il y a des cas à tester.

Un échec de test par défaut :

@Test<br/> 
public void test() {<br/> 
fail("Ce test a échoué");<br/>
} 

Instancier la classe contenant les méthodes à tester :

StringHelper helper=new StringHelper();<br/> 
@Test<br/>
public void test() {<br/>
}

 

MÉTHODES D’ASSERTIONS :

New JUnit Test

Source : Bertrand Minisclou

JUnit fournit des méthodes d’assertion, vérifiant qu’une condition, à savoir :

  • assertFalse(boolean b) : vérifie qu’une méthode retourne faux ;
  • assertTrue(boolean b) : vérifie qu’une méthode retourne vraie ;
  • assertEquals(Object e, Object a) : vérifie que a est égal à e ;
  • assertnotEquals ;
  • assertArrayEquals ;
  • assertNotNull (message, object) ;
  • assertSame(expected, actual) ;
  • assertThat (reason, actual, matcher).
    Le premier argument doit être toujours la valeur attendue (« expected value »), le second la valeur obtenue effectivement (« actual value »). En cas d’inversion, JUnit produira le message d’erreur « org.junit.ComparisonFailure ».

Syntaxe :

@Test<br/> 
public void test () {<br/> 
assertEquals(expected, actual);<br/> 
} 

Si expected != actual alors il y aura échec du test.

@Test<br/> 
public void test() {<br/>
StringHelper helper=new StringHelper();<br/>
String actual=helper.truncateAInFirst2Positions("AACD");<br/>
String expected= "CD";<br/>
assertEquals(expected, actual);<br/>
} 

Il faut tester uniquement une seule condition dans une méthode de test : une seule assertion. Les méthodes de test ne doivent pas retourner un objet : public void

ANNOTATIONS DE DÉCLENCHEMENT :
Les annotations @Before et @After permettent d’exécuter un traitement avant et après TOUTES les méthodes de test.
Les annotations @BeforeClass et @AfterClass permettent d’exécuter un traitement avant et après la classe de test. Les méthodes de AfterClass et BeforeClass doivent être statiques.

  • @Before : La méthode annotée sera lancée avant chaque test ;
  • @After : La méthode annotée sera lancée après chaque test ;
  • @BeforeClass : La méthode annotée sera lancée avant le premier test ;
  • @AfterClass : La méthode annotée sera lancée après le dernier test ;
  • @Test(timeout=100) : Permet de faire échouer le test si l’exécution de la méthode de test dépasse le délai précisé (100 millisecondes).

Syntaxe :

@Before<br/> 
public void sysouTest() {<br/> 
helper=new StringHelper();<br/> 
System.out.println("Beforetest");<br/> 
}

Syntaxe :

@After<br/> 
public void tearDown(){<br/> 
System.out.println("aftertest");<br/> 
}

TESTS D’EXCEPTIONS :
Il est utile de tester qu’une exception est bien levée, ce que les méthodes d’assertion de JUnit ne permettent pas de faire.

@Test(expected=NullPointerException.class)<br/> 
public void testArraySort_NullArray() {<br/> 
int[] numbers = null;<br/> 
Arrays.sort(numbers);<br/> 
} 

Attention cela ne remplace pas le « try catch trow » qui permet de sauter l’exception et ne pas faire échouer le test.

@Test(expected=NullPointerException.class)<br/> 
public void testArraySort_NullArray() {<br/> 
int[] numbers = null;<br/> 
try {<br/> Arrays.sort(numbers);<br/> } 
catch (NullPointerException e) {<br/> 
throw e;<br/> 
}<br/> 
}

TESTS PARAMÉTRÉS :
Les tests paramétrés sont utiles si on souhaite répéter les tests avec des inputs et outputs différents sans avoir à refaire plusieurs assertions dans une même méthode de test.

Créer un test paramétré :

@RunWith(Parameterized.class) public class<br/> 
StringHelperParameterizedTest {<br/> 

Définir les paramètres (collection d’input, output à comparer dans les méthodes de tests) :

@Parameters<br/> 
public static Collection testConditions(){<br/> 
String expectedOutputs[][]={{"AACD", "CD"}, {"ACD", "CD"}};<br/> 
return Arrays.asList(expectedOutputs);<br/> 
} 

Créer un constructeur pour utiliser les arguments locaux :

private String input;<br/> 
private String output; <br/> 
public StringHelperParameterizedTest(String input, String output) {<br/> 
this.input = input;<br/>
this.output = output;<br/> 
} 

Utiliser les paramètres dans le test :

@Test<br/> 
public void testTruncateAInFirst2PositionsWithParameters_condition2() {<br/> 
assertEquals(output,helper.truncateAInFirst2Positions(input));<br/> 
} 

Le test sera exécuté autant de fois qu’il y’a de paramètres dans la collection.
Il n’est pas possible de faire plusieurs tests paramétrés dans une seule classe car on ne peut pas avoir plusieurs constructeurs.

UTILISER LE RUNNER MOCKITO :
Mockito va créer tous les mocks qui sont déclarés, que ce soit des interfaces ou des classes.
Syntaxe :

@RunWith(MockitoJUnitRunner.class)<br/> 
public class TestMaClasse<br/> 
{<br/> 
@InjectMocks<br/> 
private MaClasse maClasse = new MaClasse(); 
<br> 
@Mock<br/> private MaComplexeClasse maComplexeClasse;

RÉALISER UN ENCHAÎNEMENT DE TESTS UNITAIRES :
Pour organiser un groupe de test, avec Junit Test Suite.
New → Junit → Junit Test Suite

@RunWith(Suite.class)<br/> 
@SuiteClasses({ ClientBOTest.class, ClientBOTestRefactored.class })<br/> 
public class AllTests {<br/> 
}

 

 

Publicité

A propos Bertrand Minisclou

Chef de projet, ingénieur développement logiciel, chargé d'études en marketing et historien.
Cet article, publié dans Programmation logiciel, est tagué , , , , . Ajoutez ce permalien à vos favoris.

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.