Les tests unitaires sont une pratique fondamentale dans le développement de logiciels qui garantit la fiabilité et l'exactitude de votre code. Dans cet article, nous explorerons les concepts clés des tests unitaires Java, y compris des exemples de code simples, le paramétrage, les tests d'exceptions, les annotations telles que @Before
, @BeforeEach
, @After
et @AfterEach
, ainsi que l'utilisation de simulacres et de stubs. .
Nous présenterons également la populaire bibliothèque Mockito pour se moquer et fournirons des conseils sur la mesure de la couverture de code à l'aide des plugins Maven.
Les tests unitaires consistent à tester des composants individuels ou des unités de code de manière isolée pour vérifier leur exactitude. L'objectif principal est de détecter et de corriger les bogues dès le début du processus de développement, en garantissant que chaque unité de code se comporte comme prévu.
Commençons par créer une classe de calculatrice simple que nous pouvons utiliser pour nos exemples. La classe Calculator
contient des opérations arithmétiques de base :
public class Calculator { public int add(int a, int b) { return a + b; } public int subtract(int a, int b) { return a - b; } public int multiply(int a, int b) { return a * b; } public int divide(int a, int b) { return a / b; } }
Pour créer un test unitaire pour notre classe Calculator
, vous pouvez utiliser le framework JUnit, un framework de test populaire pour Java. Écrivons un test unitaire simple pour la méthode add
:
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class CalculatorTest { @Test public void testAdd() { Calculator calculator = new Calculator(); int result = calculator.add(2, 3); assertEquals(5, result); } }
Dans ce test, nous importons les classes JUnit nécessaires et annotons la méthode de test avec @Test
. Nous créons ensuite une instance de la classe Calculator
et affirmons que le résultat de la méthode add
est égal à la valeur attendue (5).
Les tests paramétrés vous permettent d'exécuter la même logique de test avec plusieurs ensembles de données d'entrée. Ceci est utile pour tester une méthode avec différentes valeurs d’entrée. Pour ce faire dans JUnit, vous pouvez utiliser l'annotation @ParameterizedTest
et fournir les valeurs d'entrée et les résultats attendus en tant que paramètres. Voici un exemple :
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import static org.junit.jupiter.api.Assertions.assertEquals; public class CalculatorTest { @ParameterizedTest @CsvSource({"2, 3, 5", "4, 7, 11", "0, 0, 0"}) public void testAdd(int a, int b, int expected) { Calculator calculator = new Calculator(); int result = calculator.add(a, b); assertEquals(expected, result); } }
Dans cet exemple, l'annotation @ParameterizedTest
nous permet de fournir plusieurs ensembles de valeurs d'entrée et de résultats attendus au format CSV. La méthode de test est exécutée pour chaque combinaison, garantissant l'exactitude de la méthode add
.
Tester les exceptions est crucial pour garantir que votre code gère les erreurs de manière appropriée. Pour tester les cas d'exception, vous pouvez utiliser l'annotation @Test
avec la méthode assertThrows
fournie par JUnit.
Voici un exemple :
javaCopy codeimport org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertThrows; public class CalculatorTest { @Test public void testDivideByZero() { Calculator calculator = new Calculator(); assertThrows(ArithmeticException.class, () -> calculator.divide(5, 0)); } }
Dans ce test, nous vérifions que la division d'un nombre par zéro dans la classe Calculator
lève une ArithmeticException
.
Des annotations telles que @Before
, @BeforeEach
, @After
et @AfterEach
sont utilisées pour configurer et supprimer l'environnement de test. Ces annotations aident à gérer les tâches courantes d'initialisation et de nettoyage des tests.
@Before
et @After
s'exécutent respectivement une fois avant et après toutes les méthodes de test de la classe de test.@BeforeEach
et @AfterEach
s'exécutent avant et après chaque méthode de test.
Voici un exemple :
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class CalculatorTest { private Calculator calculator; @BeforeEach public void setUp() { calculator = new Calculator(); } @AfterEach public void tearDown() { calculator = null; } @Test public void testAdd() { int result = calculator.add(2, 3); assertEquals(5, result); } @Test public void testSubtract() { int result = calculator.subtract(5, 3); assertEquals(2, result); } }
Dans cet exemple, la méthode setUp
est annotée avec @BeforeEach
et initialise l'objet Calculator
avant chaque test. La méthode tearDown
, annotée avec @AfterEach
, nettoie les ressources après chaque test.
Les simulations et les stubs sont essentiels dans les tests unitaires lorsque vous souhaitez isoler le code testé des dépendances externes, telles que les bases de données ou les services Web. Ils permettent de simuler le comportement de ces dépendances.
Développons la classe Calculator
pour inclure une méthode qui implique une dépendance externe, puis créons un stub pour simuler cette dépendance.
public class Calculator { private ExternalService externalService; public Calculator(ExternalService externalService) { this.externalService = externalService; } public int performComplexCalculation(int a, int b) { int result = externalService.multiply(a, b); return result + externalService.getConstant(); } }
Dans cette classe Calculator
mise à jour, performComplexCalculation
s'appuie sur un ExternalService
pour la multiplication et l'obtention d'une valeur constante.
Voici comment créer un stub pour tester la classe Calculator
sans dépendre du ExternalService
réel.
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; public class CalculatorTest { @Test public void testPerformComplexCalculation() { // Create a stub for ExternalService ExternalService externalServiceStub = new ExternalService() { @Override public int multiply(int a, int b) { return a * b; } @Override public int getConstant() { return 5; // Stubbed constant value } }; // Create the Calculator with the stubbed ExternalService Calculator calculator = new Calculator(externalServiceStub); // Test the performComplexCalculation method int result = calculator.performComplexCalculation(2, 3); assertEquals(11, result); // 2*3 + 5 (constant) = 11 } }
Dans ce test, ExternalService
est remplacé dans la méthode de test en créant une classe anonyme qui remplace les méthodes nécessaires. De cette façon, la méthode de test Calculator
s’exécute sans dépendre de l’implémentation réelle de ExternalService
.
Les stubs sont utiles pour simuler le comportement de systèmes ou de dépendances externes afin d'isoler et de tester la fonctionnalité spécifique d'une classe ou d'une méthode. Cela vous permet de contrôler le comportement du stub et de vous concentrer sur l'unité testée sans avoir besoin de services externes réels.
Mockito est une bibliothèque Java populaire pour créer et gérer des objets fictifs. Disons que nous avons une classe PaymentService
qui interagit avec une passerelle de paiement externe. Nous pouvons utiliser Mockito pour créer une simulation pour la passerelle de paiement :
import org.junit.jupiter.api.Test; import static org.mockito.Mockito.*; public class PaymentServiceTest { @Test public void testProcessPayment() { PaymentGateway paymentGateway = mock(PaymentGateway.class); PaymentService paymentService = new PaymentService(paymentGateway); when(paymentGateway.charge(100.0)).thenReturn(true); boolean result = paymentService.processPayment(100.0); assertTrue(result); verify(paymentGateway, times(1)).charge(100.0); } }
Dans cet exemple, nous créons un PaymentGateway
fictif et utilisons la méthode when
pour définir son comportement. Nous appelons ensuite la méthode processPayment
sur la classe PaymentService
et vérifions que la méthode charge
a été appelée avec les paramètres attendus.
La couverture de code mesure le pourcentage de lignes de code, de branches ou d'instructions exécutées par vos tests unitaires. Il vous aide à identifier le code non testé et les zones pouvant nécessiter des tests supplémentaires.
Maven est un outil de construction populaire pour les projets Java, et vous pouvez intégrer l'analyse de couverture de code dans votre projet Maven à l'aide de plugins comme JaCoCo. Voici comment ajouter JaCoCo à votre projet :
pom.xml
de votre projet. <build> <plugins> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.7</version> <executions> <execution> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <phase>test</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
Exécutez votre build Maven avec la commande suivante :
mvn clean verify
Après avoir exécuté la build, vous pouvez trouver le rapport de couverture de code dans le fichier target/site/jacoco/index.html
. Ce rapport fournit des informations détaillées sur la couverture de votre code par vos tests unitaires.
Les tests unitaires Java sont une pratique essentielle pour garantir la fiabilité et l'exactitude de votre code. Avec des outils comme JUnit et Mockito, vous pouvez écrire des tests unitaires et des simulations efficaces pour vos composants.
En intégrant l'analyse de couverture de code à Maven et JaCoCo, vous pouvez vous assurer que vos tests couvrent une partie importante de votre base de code. Tester et maintenir régulièrement des tests unitaires vous aidera à produire des applications Java robustes et de haute qualité.
Dans la prochaine partie de cette série, nous explorerons les tests d'intégration, un aspect critique des tests logiciels qui implique de tester les interactions entre différents composants et services pour garantir le bon fonctionnement global du système.
Restez à l'écoute pour la deuxième partie, où nous plongerons dans le monde passionnant des tests d'intégration !