CodeGym /Blog Java /Random-FR /Tests unitaires en Java avec JUnit
Auteur
Edward Izraitel
Software Engineer at Champions Oncology

Tests unitaires en Java avec JUnit

Publié dans le groupe Random-FR

Qu'est-ce que les tests unitaires en Java ?

Avant d'aborder l'apprentissage de JUnit en Java, passons brièvement en revue ce qu'est le test unitaire et pourquoi il est si populaire (si vous connaissez déjà ce genre de choses, passez à 'Comment puis-je écrire un test JUnit en Java ?'). Les tests unitaires en Java rendent le développement de logiciels à grande échelle beaucoup plus efficace et sans effort. Cela peut aider à la fois les individus et les équipes à réduire d'innombrables heures de débogage et à rationaliser énormément le processus de collaboration. Tests unitaires en Java avec JUnit - 1

https://junit.org/junit4/

L'idée essentielle des tests unitaires est la suivante : écrivez des tests atomiques de fonctionnalités individuelles (appelés tests unitaires) et ajoutez lentement plus de fonctionnalités après avoir testé et vérifié que les précédentes fonctionnent. C'est une idée extrêmement simple mais puissante. Comme exemple de ce à quoi ce processus pourrait ressembler, imaginez que vous construisiez une calculatrice scientifique virtuelle. En plus des opérateurs arithmétiques apparents ( +, -, x, %), cette calculatrice aurait des fonctionnalités avancées qui nécessitent d'autres sous-fonctionnalités pour fonctionner en son sein. Pour calculer les exposants, votre calculatrice doit pouvoir multiplier correctement. Ainsi, une approche de test unitaire pour construire et tester cette calculatrice serait :
  • Ecrire une fonction d'addition. Testez-le soigneusement, changez-le, répétez jusqu'à ce que cela fonctionne.
  • Faites de même pour les fonctions de soustraction, multiplication, division.
  • Utilisez ces opérateurs de base pour écrire des fonctions d'opérateur plus avancées comme les exposants, puis testez également ces fonctions.
Cela garantit que les fonctionnalités qui s'appuient sur d'autres sous-fonctionnalités plus petites fonctionnent correctement par elles-mêmes, mais qu'elles ne contiennent pas de sous-fonctionnalités défectueuses. Par exemple, si je teste la fonction d'exposant et que quelque chose ne va pas, je sais que le bogue n'est probablement pas dans la sous-fonctionnalité de multiplication, car la fonction de multiplication a déjà été largement testée. Cela élimine considérablement la quantité totale de code dont j'ai besoin pour revenir en arrière et inspecter pour trouver le bogue. Espérons que cet exemple trivial indique clairement comment le processus de réflexion autour des tests unitaires est structuré. Mais comment les tests unitaires interagissent-ils avec le reste du processus de développement logiciel ? Que se passe-t-il si vous avez des fonctionnalités encore plus complexes, qui doivent pouvoir fonctionner et communiquer ensemble ? Les tests unitaires sont insuffisants pour garantir que ces fonctionnalités complexes peuvent fonctionner correctement ensemble. En fait, ce n'est que la première étape des quatre niveaux de test de logiciel (j'utilise des majuscules parce que je fais référence à la norme de l'industrie ou à l'approche la plus courante pour tester un logiciel). Les trois dernières étapes sontTests d'intégration , tests système et tests d'acceptation. Tout cela signifie probablement exactement ce que vous pensez qu'ils font, mais permettez-moi de clarifier : les tests d'intégration sont ce que nous ferions pour garantir que les "fonctionnalités complexes" mentionnées ci-dessus interagissent correctement les unes avec les autres. (par exemple, s'assurer que la calculatrice peut gérer « 3 + 7 * 4 - 2 ») Le test du système teste la conception globale d'un système particulier ; il existe souvent plusieurs systèmes de fonctionnalités complexes fonctionnant ensemble dans un produit, vous devez donc les regrouper en systèmes et les tester individuellement. (par exemple, si vous construisiez une calculatrice graphique, vous construiriez d'abord le «système» arithmétique pour traiter les nombres, en testant jusqu'à ce qu'il fonctionne comme prévu, puis vous construiriez et testeriez le «système» graphique pour gérer le retrait, comme il s'appuierait sur le système arithmétique). Les tests d'acceptation sont des tests au niveau de l'utilisateur ; il s'agit de voir si tous les systèmes peuvent fonctionner en synchronisation pour créer un produit fini prêt à être accepté par les utilisateurs (par exemple, les utilisateurs testant la calculatrice). Les développeurs de logiciels peuvent parfois ignorer cette dernière étape du processus, car les entreprises demandent souvent à d'autres employés de déployer des tests utilisateur (bêta) séparément.

Comment écrire un test JUnit en Java ?

Maintenant que vous avez une idée plus claire des avantages et des limites des tests unitaires, examinons un peu de code ! Nous utiliserons un framework de test Java populaire appelé JUnit (un autre populaire est TestNG, que vous pouvez également utiliser si vous le souhaitez. Ils sont très similaires, syntaxiquement ; TestNG est inspiré de JUnit). Vous pouvez télécharger et installer JUnit ici . Pour cet exemple de code, nous continuerons à partir de l'exemple de "calculatrice scientifique" que j'ai mentionné plus tôt ; c'est assez simple à comprendre, et le code de test est super facile. La pratique conventionnelle consiste à écrire des classes de test distinctes pour chacune de vos classes, c'est donc ce que nous ferons. Supposons qu'à ce stade, nous ayons un Math.javafichier contenant toutes les fonctions mathématiques (y compris Math.add), et que nous écrivions unMathTests.javafichier dans le même package. Maintenant, configurons les instructions d'importation et le corps de la classe : (POSSIBLE JUnit INTERVIEW QUESTION : on peut vous demander où placer votre test JUnit et si vous devez ou non importer vos fichiers source. Si vous écrivez vos classes de test dans le même package que vos classes principales, vous n'avez pas besoin d'instructions d'importation pour vos fichiers source dans la classe de test. Sinon, assurez-vous d'importer vos fichiers source !)

import org.junit.jupiter.Test;    //gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; //less typing :) 

public class MathTests {
	//...
}
La première instruction d'importation nous donne l' @Testen-tête. Nous écrivons ' @Test' directement au-dessus de chaque définition de fonction de test, afin que JUnit sache qu'il s'agit d'un test unitaire singulier qui peut être exécuté séparément. Plus tard, je vous montrerai comment exécuter des tests unitaires spécifiques à l'aide de cet en-tête. La deuxième instruction d'importation nous évite un peu de frappe. La fonction JUnit principale que nous utilisons pour tester nos fonctions est to Assert.assertEquals(), qui prend deux paramètres (valeur réelle et valeur attendue) et s'assure qu'ils sont égaux. Avoir cette deuxième instruction d'importation nous permet simplement de taper ' assertEquals(...' au lieu d'avoir à spécifier à chaque fois de quel paquet il fait partie. Écrivons maintenant un cas de test très simple pour vérifier que 2 + 2 est bien 4 !

import org.junit.jupiter.Test; // gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; // less typing :) 


public class MathTests {
	@Test
	public void add_twoPlusTwo_returnsFour(){
	final int expected = 4;
	final int actual = Math.add(2, 2);
	assertEquals(“2+2 is 4”, actual, expected);
	}
}
Passons en revue chacune des cinq lignes de la fonction de test et ce qu'elles font : Ligne 5 : cet @Testen-tête spécifie que la définition de fonction ci-dessous add_twoPlusTwo_returnsFour()est en effet une fonction de test que JUnit peut exécuter séparément. Ligne 6 : Il s'agit de la signature de la fonction pour notre cas de test. Les cas de test sont toujours très singuliers ; ils ne testent qu'un exemple spécifique, tel que 2+2=4. Il est de convention de nommer vos cas de test sous la forme « [function]_[params]_returns[expected]()», où [function]est le nom de la fonction que vous testez, [params]sont les paramètres d'exemple spécifiques que vous testez et [expected]est la valeur de retour attendue de la fonction. Les fonctions de test ont presque toujours un type de retour de ' void' car le point principal de toute la fonction est de s'exécuterassertEquals, qui indiquera à la console si votre test a réussi ou non ; vous n'avez besoin d'aucune autre donnée pour être retournée n'importe où. Ligne 7 : Nous déclarons une finalvariable ' ' de type de retour Math.add (int), et la nommons 'expected' par convention. Sa valeur est la réponse que nous attendons (4). Ligne 8 : Nous déclarons une finalvariable ' ' de type de retour Math.add (int), et la nommons 'réel' par convention. Sa valeur est le résultat de Math.add(2, 2). Ligne 9 : La ligne dorée. C'est la ligne qui compare le réel et le prévu et nous indique que nous avons réussi le test uniquement s'ils sont égaux. Le premier paramètre passé "2+2 est 4" est une description de la fonction de test.

Et si ma fonction devait lever une exception ?

Si votre exemple de test spécifique doit lever une exception au lieu d'affirmer qu'une valeur réelle et une valeur attendue sont égales, alors JUnit a un moyen de clarifier cela dans l' @Testen-tête. Jetons un coup d'œil à un exemple ci-dessous. En supposant que nous ayons une fonction Math.javaappelée Math.divide, nous voulons nous assurer que les entrées ne peuvent pas être divisées par 0. Au lieu de cela, essayer d'appeler Math.divide(a, 0)n'importe quelle valeur 'a' devrait lever une exception ( ArithmeticException.class). Nous le spécifions dans l'en-tête en tant que tel :

import org.junit.jupiter.Test; // gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; // less typing :) 


public class MathTests {
	@Test (expectedExceptions = ArithmeticException.class)
	public void divide_byZero_throwsException() throws ArithmeticException{
	Math.divide(1, 0);
	}
}
Vous pouvez avoir plus d'une exception pour expectedExceptions, assurez-vous simplement d'utiliser des crochets et des virgules pour répertorier vos classes d'exception, en tant que telles :

expectedException = {FirstException.class, SecondException.class, … }

Comment exécuter mes tests JUnit en Java ?

Comment ajouter JUnit à IntelliJ : https://stackoverflow.com/questions/19330832/setting-up-junit-with-intellij-idea Vous pouvez exécuter votre projet comme vous le feriez normalement pour les tests. L'exécution de tous les tests dans une classe de test les exécutera dans l'ordre alphabétique. Dans JUnit 5, vous pouvez ajouter une priorité aux tests en ajoutant une @Orderbalise. Un exemple:

@TestMethodOrder(OrderAnnotation.class)
public class Tests {
…
@Test
@Order(2)
public void a_test() { … }

@Test
@Order (1)
public void b_test() { … }
…
}
Même si a_test()vient avant b_test()alphabétiquement et dans le code, b_test()s'exécutera avant a_test()ici, car 1 vient avant 2 dans Order. C'est à peu près tout pour les bases de JUnit. Maintenant, abordons quelques questions d'entretien JUnit courantes et apprenons-en plus sur JUnit en cours de route !

Questions d'entrevue JUnit (informations supplémentaires)

Ici, j'ai rassemblé les questions d'entretien JUnit les plus populaires. Si vous avez quelque chose à ajouter, n'hésitez pas à le faire dans les commentaires ci-dessous. Q : Quelle méthode pouvez-vous appeler dans votre méthode de test pour échouer automatiquement à un test ? A : échec ("description de l'erreur ici !" ); Q : Vous testez une classe Chien ; pour tester un objet Dog, vous devez l'instancier avant de pouvoir exécuter des tests dessus. Vous écrivez donc une fonction setUp() pour instancier le Dog. Vous ne voulez exécuter cette fonction qu'une seule fois pendant tous les tests. Que devez-vous mettre directement au-dessus de la signature de la fonction setUp() pour que JUnit sache exécuter setUp() avant d'exécuter les tests ? R : @BeforeClass (@BeforeAll dans JUnit 5) Q :Quelle doit être la signature de fonction de la fonction setUp() décrite ci-dessus ? A : vide statique public. Toute fonction avec @BeforeClass (@BeforeAll dans JUnit 5) ou @AfterClass (@AfterAll dans JUnit 5) doit être statique. Q : Vous avez terminé de tester la classe Chien. Vous écrivez la fonction void tearDown() qui nettoie les données et affiche les informations sur la console après chaque test. Vous voulez que cette fonction s'exécute après chaque test. Que devez-vous mettre directement au-dessus de la signature de la fonction tearDown() pour que JUnit sache exécuter tearDown() après avoir exécuté chaque test ? R : @After (@AfterEach dans JUnit 5)
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION