Test unitaire avec JUnit 4
Transcription
Test unitaire avec JUnit 4
Tests unitaires en TDD avec JUnit sous Netbeans I. Introduction Un test unitaire est un bout de code écrit par un développeur pour tester une fonctionnalité dans un code. Le test unitaire est utilisé pour s'assurer que la fonctionnalité écrite fonctionne correctement mais aussi pour s'assurer que cette fonctionnalité continue de fonctionner après des modifications apportées au code lors de la reingénierie. JUnit est un framework de test unitaire pour le langage de programmation Java. JUnit est intégré à Netbeans. II. Organisation des fichiers Les tests unitaires doivent être dans un dossier nommé test. L'organisation de ce dossier suit la même organisation que le dossier src. Un fichier dans test a donc le même nom de paquet que la classe qu'il teste, par exemple la classe fr.ensicaen.ecole.toto.MaClasse.java du dossier src aura comme nom de fichier fr.ensicaen.ecole.toto.MaClasseTest.java dans le dossier test. École Nationale Supérieure d'Ingénieurs & Groupe de Laboratoires, C.N.R.S. 6, Boulevard Maréchal Juin, F-14050 CAEN cedex http://www.ensicaen.fr 2 ■ Tests unitaires en TDD avec JUnit sous Netbeans III. JUnit sous Netbeans III.1 Créer une classe de test Dans le cas d'un développement en TDD, on commence par créer la classe de test avant la classe à tester. La création se fait par le menu contextuel associé au paquet où l'on désire mettre le fichier de test avec l'item New > JUnit Test... Par convention, on nomme MaClasseTest.java la classe de test pour la classe MaClasse.java à tester. III.2 Créer la classe à tester Le menu contextuel à l'intérieur d'un fichier permet de naviguer entre le fichier source et le fichier de test : ► Naviguate > Go to Source ► Naviguate > Go to Test/Tested class ENSICAEN - Spécialité Informatique La première visite du fichier source va créer le fichier source correspondant dans le bon paquet. III.3 Exécuter un test Pour exécuter un fichier de test, il suffit d'utiliser le menu contextuel par un clic droit sur le nom du fichier test dans le navigateur puis d'utiliser l'option Test File. Pour exécuter tous les fichiers de test en une seule fois, il faut utiliser le menu contextuel associé au nom du projet dans le navigateur et utiliser l'option Test. Une fenêtre s'ouvre et permet de visualiser le résultat des tests : III.4 Exemple introductif de cas de test Le programme suivant utilise des méthodes statiques pour faire des calculs arithmétiques. Toutes les méthodes à tester doivent être publiques ou protégées (si Tests unitaires en TDD avec JUnit sous Netbeans ■ 3 le dossier test respecte l'organisation en paquet du fichier source), mais pas privées, afin d'être testables. 1 public class StaticCalculation { 2 public static int add( int number1, int number2 ) { 3 return number1 + number2; 4 } 5 public static int sub( int number1, int number2 ) { 6 return number1 - number2; 7 } 8 public static int mul( int number1, int number2 ) { 9 return number1 * number2; 10 } 11 public static int div( int number1, int number2 ) { 12 return number1 / number2; 13 } 14 } La classe de test est : III.5 Annotations La Table 1 donne un aperçu des annotations de JUnit 4.x. Table 1: Annotations. Annotation @Test Description Marque une méthode comme une méthode de test. public void whenCaseThenResult() @Before Exécute cette méthode avant chaque test. Elle permet de public void setup() préparer l'environnement du test, e.g. lire des valeurs, initialiser la classe. @After Exécute cette méthode après chaque test. public void tearDown() @BeforeClass Exécute cette méthode avant le début de tous les tests. public static void setupBeforeClass() Cela peut être utilisé pour faire des traitements chronophages, par exemple pour se connecter à une base de données. @AfterClass Exécute cette méthode après la série de tous les tests. ENSICAEN - Spécialité Informatique 1 import static org.junit.Assert.assertEquals; 2 import org.junit.Test; 3 4 public class StaticCalculationTest { 5 @Test 6 public void addSubTest() { 7 assertEquals(3, StaticCalculation.add(1, 2)); 8 assertEquals(1, StaticCalculation.sub(2, 1)); 9 } 10 @Test 11 public void mulDivTest() { 12 assertEquals(6, StaticCalculation.mul(2, 3)); 13 assertEquals(0.5, StaticCalculation.div(1, 2), 1e-8); 14 } } 4 ■ Tests unitaires en TDD avec JUnit sous Netbeans Annotation Description public static void tearDownAfterClass() Cela permet de nettoyer l'environnement de test, par exemple se déconnecter d'une base de données. @Ignore Ignore ce test. Utile par exemple quand le test n'est pas public void method() encore prêt. @Test(expected=Exception.class) Teste si la méthode lève bien l'exception attendue. public void method() @Test(timeout=100) Fait échouer le test si la durée de traitement de la public void method() méthode est supérieure à 100 millisecondes. III.6 Les formes de Assert La Table 2 récapitule les principales assertions utilisables pour les tests. Table 2: Liste des fonctions d'assertion. Fonction Description ENSICAEN - Spécialité Informatique assertTrue([message], condition) Vérifie que la condition est vraie. assertFalse([message], condition) Vérifie que la condition est fausse. assertsEquals([message], expected, actual) Teste si les valeurs sont égales. Note : pour les tableaux, seule la référence est vérifiée, pas le contenu des tableaux. assertsEquals([message], expected, actual, tolerance) Vérifie l'égalité entre des doubles ou des flottants à une valeur de précision près. assertNull([message], object) Vérifie que l'objet est bien nul. assertNotNull([message], object) Vérifie que l'objet n'est pas nul. assertSame([message], expected, actual) Vérifie que les deux variables pointent vers le même objet assertNotSame([message], expected, actual) Vérifie que les deux variables ne pointent pas vers le même objet. III.6.1 Utilisation de base Cette section introduit les annotations de JUnit 4 à travers un exemple. 1 public class JUnitTest1 { 2 3 private Collection _collection; 4 5 @BeforeClass 6 public static void oneTimeSetUp() { 7 System.out.println("@BeforeClass - oneTimeSetUp"); 8 } 9 10 @AfterClass 11 public static void oneTimeTearDown() { 12 System.out.println("@AfterClass - oneTimeTearDown"); 13 } 14 15 @Before 16 public void setUp() { Tests unitaires en TDD avec JUnit sous Netbeans ■ 5 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 } _collection = new ArrayList(); System.out.println("@Before - setUp"); } @After public void tearDown() { _collection.clear(); System.out.println("@After - tearDown"); } @Test public void testEmptyCollection() { assertTrue(_collection.isEmpty()); System.out.println("@Test - testEmptyCollection"); } @Test public void testOneItemCollection() { _collection.add("itemA"); assertEquals(1, _collection.size()); System.out.println("@Test - testOneItemCollection"); } @BeforeClass - oneTimeSetUp @Before - setUp @Test - testEmptyCollection @After - tearDown @Before - setUp @Test - testOneItemCollection @After - tearDown 40 @AfterClass - oneTimeTearDown Les méthodes “@BeforeClass” et “@AfterClass” doivent être déclarées en tant que méthodes statiques. III.6.2 Tester des exceptions Dans l'exemple suivant, le test a pour but de vérifier qu'il y a bien une exception de type ArithmeticException qui est levée puisque qu'il y a une division par 0. 1 public class JUnitTest2 { 2 @Test(expected = ArithmeticException.class) 3 public void divisionWithException() { 4 int i = 1/0; 5 } 6 } III.6.3 Ignorer un test L'annotation “@Ignore” signifie d'ignorer le test de la méthode. Cela est utile par exemple lorsque la méthode n'est pas encore prête. 1 public class JUnitTest3 { 2 ENSICAEN - Spécialité Informatique Résultat : 6 ■ Tests unitaires en TDD avec JUnit sous Netbeans 3 4 5 6 7 8 } @Ignore("Not Ready to Run") @Test public void divisionWithException() { System.out.println("Method is not ready yet"); } III.6.4 Tester la durée d'une méthode Le paramètre timeout de l'annotation “@Test” permet de tester la durée maximale de l'exécution d'une méthode en millisecondes. 1 public class JUnitTest4 { 2 @Test(timeout = 1000) 3 public void infinity() { 4 while (true); 5 } 6 } Dans l'exemple précédent, la méthode infinity() ne finira jamais, donc le moteur JUnit la marque comme échec après 1000 millisecondes et lèvera ENSICAEN - Spécialité Informatique l'exception suivante : java.lang.Exception: test timed out after 1000 milliseconds III.6.5 Exécuter une suite de tests Le moteur JUnit n'utilise aucun ordre a priori sur les tests à effectuer. Cela suppose que les tests unitaires soient indépendants. Les annotations @RunWith and @Suite permettent d'exécuter une suite de tests dans l'ordre spécifié. L'exemple suivant enchaîne les tests JUnitTest1 et JUnitTest2 après que le test JUnitTest5 ait été exécuté lui-même. 1 2 3 4 @RunWith(Suite.class) @Suite.SuiteClasses({JUnitTest1.class, JUnitTest2.class}) public class JUnitTest5 { } Résultat : BeforeClass - oneTimeSetUp @Before - setUp @Test - testEmptyCollection @After - tearDown @Before - setUp @Test - testOneItemCollection @After - tearDown @AfterClass - oneTimeTearDown III.6.6 Tests paramétrés Les tests paramétrés permettent de varier les valeurs de paramètres pour les tests. 5 @RunWith(Parameterized.class) Tests unitaires en TDD avec JUnit sous Netbeans ■ 7 6 public class MyUnitTest { 7 // ... 8 } La méthode marquée avec @parameters doit être statique et retourner une Collection. Les éléments ajoutés à cette collection doivent être transmis au constructeur du test unitaire comme argument. Cette partie est très importante. 1 @RunWith(Parameterized.class) 2 public class MyUnitTest { 3 private Integer _testNumber; 4 5 public MyUnitTest( Integer myTestNumber ) { 6 _testNumber = myTestNumber; 7 } 8 9 @Parameters 10 public static Collection testNumbers() { 11 return Arrays.asList(1, 2, 3); 12 } 13 } La méthode annotée avec @parametrized peut évidemment être un générateur Et voici un cas de test paramétré : 1 @RunWith(Parameterized.class) 2 public class PrimeNumberValidatorTest { 3 private Integer _primeNumber; 4 private Boolean _expectedValidation; 5 private PrimeNumberValidator _primeNumberValidator; 6 7 @Before 8 public void initialize() { 9 _primeNumberValidator = new PrimeNumberValidator(); 10 } 11 12 // Chaque parametre doit être placé comme argument 13 // A chaque exécution du moteur de test, il passe 14 // les valeurs comme parametres au contructeur. 15 public PrimeNumberValidatorTest(Integer primeNumber, Boolean expected ) { 16 _primeNumber = primeNumber; 17 _expectedValidation = expected; 18 } 19 20 @Parameters 21 public static Collection primeNumbers() { 22 return Arrays.asList(new Object[][] { 23 { 2, true }, { 6, false }, 24 { 19, true }, { 22, false }}); 25 } 26 27 // Sera exécuté 4 fois puisque nous avons définis 4 paramètres 28 @Test 29 public void testPrimeNumberValidator() { 30 assertEquals(_expectedValidation, 31 _primeNumberValidator.validate(primeNumber)); ENSICAEN - Spécialité Informatique de nombres aléatoires qui retourne une collection de nombres. 8 ■ Tests unitaires en TDD avec JUnit sous Netbeans 32 } 33 } 1 class PrimeNumberValidator { 2 public Boolean validate(final Integer primeNumber) { 3 for (int i = 2; i < (primeNumber / 2); i++) { 4 if (primeNumber % i == 0) { 5 return false; 6 } 7 } 8 return true; 9 } 10 } ENSICAEN - Spécialité Informatique