Logo Parasoft Rechercher

Découvrez GoogleTest certifié TÜV avec Agentic AI pour les tests C/C++ !
Plus de détails »

Blog Parasoft

Comment créer des tests paramétrés JUnit

By Brian McGlauflin • 19 septembre • 8 min de lecture
19 septembre | 8 min de lecture
By Brian McGlauflin
Graphique d'un développeur écrivant le code source dans plusieurs écrans d'éditeur de texte

L'écriture de code de test paramétré peut représenter beaucoup de travail car chaque test nécessite une grande partie du code correctement écrit. Heureusement, voici comment vous pouvez utiliser Parasoft Jtest pour générer automatiquement vos tests paramétrés sans écrire de codes passe-partout.

Les tests paramétrés sont un bon moyen de définir et d'exécuter plusieurs cas de test, où la seule différence entre eux réside dans les données. Ici, nous examinons trois frameworks différents couramment utilisés avec les tests JUnit.

Lors de l'écriture de tests unitaires, il est courant d'initialiser les paramètres d'entrée de la méthode et les résultats attendus dans la méthode de test elle-même. Dans certains cas, l'utilisation d'un petit ensemble d'entrées suffit; cependant, il existe des cas dans lesquels nous devons utiliser un grand ensemble de valeurs pour vérifier toutes les fonctionnalités de notre code. Les tests paramétrés sont un bon moyen de définir et d'exécuter plusieurs cas de test, où la seule différence entre eux réside dans les données. Ils peuvent valider le comportement du code pour une variété de valeurs, y compris les cas de bordure. Le paramétrage des tests peut augmenter la couverture du code et garantir que le code fonctionne comme prévu.

Il existe un certain nombre de bons cadres de paramétrage pour Java. Dans cet article, nous examinerons trois frameworks différents couramment utilisés avec les tests JUnit, avec une comparaison entre eux et des exemples de la façon dont les tests sont structurés pour chacun. Enfin, nous explorerons comment simplifier et accélérer la création de tests paramétrés.

Cadres de test paramétrés JUnit

Comparons les 3 frameworks les plus courants: JUnit 4, JunitParams et JUnit 5. Chaque framework de paramétrage JUnit a ses propres forces et faiblesses.

Unité 4

Avantages :

  • Il s'agit du cadre de paramétrage intégré à JUnit 4, il ne nécessite donc aucune dépendance externe supplémentaire.
  • Il prend en charge les anciennes versions de Java (JDK 7 et antérieures).

Inconvénients :

  • Les classes de test utilisent des champs et des constructeurs pour définir des paramètres, ce qui rend les tests plus détaillés.
  • Il nécessite une classe de test distincte pour chaque méthode testée.

JunitParams

Avantages :

  • Simplifie la syntaxe des paramètres en autorisant la transmission directe des paramètres à une méthode de test.
  • Permet plusieurs méthodes de test (chacune avec leurs propres données) par classe de test.
  • Prend en charge les sources de données CSV, ainsi que les valeurs basées sur les annotations (aucune méthode requise).

Inconvénients :

  • Nécessite que le projet soit configuré avec la dépendance JunitParams.
  • Lors de l'exécution et du débogage des tests, tous les tests de la classe doivent être exécutés - il n'est pas possible d'exécuter une seule méthode de test dans une classe de test.

Unité 5

Avantages :

  • Ce cadre de paramétrage est intégré à JUnit 5 et améliore ce qui était inclus avec JUnit 4.
  • A une syntaxe de paramètre simplifiée comme JunitParams.
  • Prend en charge plusieurs types de sources d'ensembles de données, y compris CSV et annotation (aucune méthode requise).
  • Même si aucune dépendance supplémentaire n'est requise, plusieurs fichiers .jar sont nécessaires.

Inconvénients :

  • Nécessite Java 8 et une version plus récente du système de construction (Gradle 4.6 ou Maven Surefire 2.21).
  • Peut ne pas être encore pris en charge dans votre IDE (au moment de la rédaction de cet article, seuls Eclipse et IntelliJ prennent en charge JUnit 5).

Exemple

À titre d'exemple, supposons que nous ayons une méthode qui traite les demandes de prêt pour une banque. Nous pourrions écrire un test unitaire qui spécifie le montant de la demande de prêt, le montant de l'acompte et d'autres valeurs. Nous créerions alors des assertions qui valident la réponse - le prêt peut être approuvé ou rejeté, et la réponse peut spécifier les conditions du prêt.

Par exemple :

public LoanResponse requestLoan(float loanAmount, float downPayment, float availableFunds)
{
LoanResponse response = new LoanResponse();
response.setApproved(true);

if (availableFunds < downPayment) {
response.setApproved(false);
response.setMessage("error.insufficient.funds.for.down.payment");
return response;
}
if (downPayment / loanAmount < 0.1) {
response.setApproved(false);
response.setMessage("error.insufficient.down.payment");
}

return response;
}

Voir Raw

exemple_test_paramètre_1.java

Hébergé par GitHub

Tout d'abord, regardons un test régulier pour la méthode ci-dessus:

@Test
public void testRequestLoan() throws Throwable
{
// Given
LoanProcessor underTest = new LoanProcessor();

// When
LoanResponse result = underTest.requestLoan(1000f, 200f, 250f);

// Then
assertNotNull(result);
assertTrue(result.isApproved());
assertNull(result.getMessage());
}

Voir Raw

exemple_test_paramètre_2.java

Hébergé par GitHub

Dans cet exemple, nous testons notre méthode en demandant un prêt de 1000 200 $, avec un acompte de 250 $ et en indiquant que le demandeur dispose de XNUMX $ de fonds disponibles. Le test valide ensuite que le prêt a été approuvé et n'a pas fourni de message dans la réponse.

Afin de garantir le bon fonctionnement de notre méthode `requestLoan()` , nous devons effectuer des tests avec différents montants d'acompte, de prêt demandé et de fonds disponibles. Par exemple, testons une demande de prêt d'un million de dollars sans acompte, qui devrait être rejetée. Nous pourrions simplement dupliquer le test existant avec des valeurs différentes, mais comme la logique de test reste la même, il est plus efficace de paramétrer le test.

Nous paramétrerons le montant du prêt demandé, l'acompte et les fonds disponibles, ainsi que les résultats attendus: si le prêt a été approuvé, et le message renvoyé après validation. Chaque ensemble de données de demande, ainsi que ses résultats attendus, deviendra son propre scénario de test.

Un exemple de test paramétré utilisant JUnit 4 paramétré

Commençons par un exemple Junit 4 Parameterized. Pour créer un test paramétré, nous devons d'abord définir les variables du test. Nous devons également inclure un constructeur pour les initialiser:

@RunWith(Parameterized.class)
public class LoanProcessorParameterizedTest {
float loanAmount;
float downPayment;
float availableFunds;
boolean expectApproved;
String expectedMessage;
public LoanProcessorParameterizedTest(float loanAmount, float downPayment,
float availableFunds, boolean expectApproved, String expectedMessage)
{
this.loanAmount = loanAmount;
this.downPayment = downPayment;
this.availableFunds = availableFunds;
this.expectApproved = expectApproved;
this.expectedMessage = expectedMessage;
}
// ...
}

Voir Raw

exemple_test_paramètre_3.java

Hébergé par GitHub

Ici, nous constatons que le test utilise l' annotation `@RunWith` pour spécifier qu'il sera exécuté avec l'exécuteur paramétré JUnit 4. Cet exécuteur sait rechercher une méthode fournissant l'ensemble de valeurs pour le test (annotée avec `@Parameters` ), initialiser correctement le test et exécuter les tests avec plusieurs lignes.

Notez que chaque paramètre est défini comme un champ de la classe de test, et que le constructeur initialise ces valeurs (vous pouvez également injecter des valeurs dans les champs à l'aide de l' annotation `@Parameter` si vous ne souhaitez pas créer de constructeur). Pour chaque ligne de l'ensemble de valeurs, l' exécuteur paramétré instancie la classe de test et exécute chaque test de cette classe.

Ajoutons une méthode qui fournit les paramètres au lanceur paramétré :

@Parameters(name = "Run {index}: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}")
public static Iterable<Object[]> data() throws Throwable
{
return Arrays.asList(new Object[][] {
{ 1000.0f, 200.0f, 250.0f, true, null }
});
}

Voir Raw

exemple_test_paramètre_4.java

Hébergé par GitHub

Les ensembles de valeurs sont construits sous forme de liste de tableaux d'objets par la méthode `data()` , annotée avec `@Parameters` . Notez que `@Parameters` définit le nom du test à l'aide d'espaces réservés, qui seront remplacés lors de l'exécution. Cela facilite la lecture des valeurs dans les résultats des tests, comme nous le verrons plus loin. Actuellement, il n'y a qu'une seule ligne de données, testant le cas où le prêt devrait être approuvé. Nous pouvons ajouter d'autres lignes pour étendre la couverture de la méthode testée.

@Parameters(name = "Run {index}: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}")
public static Iterable<Object[]> data() throws Throwable
{
return Arrays.asList(new Object[][] {
{ 1000.0f, 200.0f, 250.0f, true, null },
{ 1000.0f, 50.0f, 250.0f, false, "error.insufficient.down.payment" },
{ 1000.0f, 200.0f, 150.0f, false, "error.insufficient.funds.for.down.payment" }
});
}

Voir Raw

exemple_test_paramètre_5.java

Hébergé par GitHub

Ici, nous avons un cas de test où le prêt serait approuvé, et deux cas dans lesquels il ne devrait pas être approuvé pour des raisons différentes. Nous pouvons souhaiter ajouter des lignes dans lesquelles des valeurs nulles ou négatives sont utilisées, ainsi que des conditions aux limites de test.

Nous sommes maintenant prêts à créer la méthode de test:

@Test
public void testRequestLoan() throws Throwable
{
// Given
LoanProcessor underTest = new LoanProcessor();

// When
LoanResponse result = underTest.requestLoan(loanAmount, downPayment, availableFunds);

// Then
assertNotNull(result);
assertEquals(expectApproved, result.isApproved());
assertEquals(expectedMessage, result.getMessage());
}

Voir Raw

exemple_test_paramètre_6.java

Hébergé par GitHub

Ici, nous faisons référence aux champs lors de l'appel de la méthode requestLoan() et de la validation des résultats.

Exemple JunitParams

La bibliothèque JunitParams simplifie la syntaxe des tests paramétrés en permettant de passer directement les paramètres à la méthode de test. Les valeurs des paramètres sont fournies par une méthode distincte dont le nom est référencé dans l' annotation @Parameters .

@RunWith(JUnitParamsRunner.class)
public class LoanProcessorParameterizedTest2 {

@Test
@Parameters(method = "testRequestLoan_Parameters")
public void testRequestLoan(float loanAmount, float downPayment, float availableFunds,
boolean expectApproved, String expectedMessage) throws Throwable
{
...
}

@SuppressWarnings("unused")
private static Object[][] testRequestLoan_Parameters() throws Throwable {
// Parameters: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}
return new Object[][] {
{ 1000.0f, 200.0f, 250.0f, true, null },
{ 1000.0f, 50.0f, 250.0f, false, "error.insufficient.down.payment"},
{ 1000.0f, 200.0f, 150.0f, false, "error.insufficient.funds.for.down.payment" }
};
}
}

Voir Raw

exemple_test_paramètre_7.java

Hébergé par GitHub

JunitParams présente l'avantage supplémentaire de prendre en charge l'utilisation de fichiers CSV pour fournir des valeurs en plus de fournir les valeurs dans le code. Cela permet au test d'être découplé des données et des valeurs de données à mettre à jour sans mettre à jour le code.

Exemple Junit 5

JUnit 5 résout certaines des limitations et des lacunes de JUnit 4. Comme JunitParams, Junit 5 simplifie également la syntaxe des tests paramétrés. Les changements de syntaxe les plus importants sont:

  • La méthode de test est annotée avec @Test Paramétré au lieu de @Tester
  • La méthode de test accepte les paramètres directement, au lieu d'utiliser des champs et un constructeur
  • Le @Courir avec l'annotation n'est plus nécessaire

La définition du même exemple dans Junit 5 ressemblerait à ceci:

public class LoanProcessorParameterizedTest {

@ParameterizedTest(name="Run {index}: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}")
@MethodSource("testRequestLoan_Parameters")
public void testRequestLoan(float loanAmount, float downPayment, float availableFunds,
boolean expectApproved, String expectedMessage) throws Throwable
{
...
}

static Stream<Arguments> testRequestLoan_Parameters() throws Throwable {
return Stream.of(
Arguments.of(1000.0f, 200.0f, 250.0f, true, null),
Arguments.of(1000.0f, 50.0f, 250.0f, false, "error.insufficient.down.payment"),
Arguments.of(1000.0f, 200.0f, 150.0f, false, "error.insufficient.funds.for.down.payment")
);
}
}

Voir Raw

exemple_test_paramètre_8.java

Hébergé par GitHub

Créez efficacement des tests paramétrés

Comme on peut l'imaginer, écrire le test paramétré ci-dessus peut s'avérer fastidieux. Chaque framework de test paramétré requiert un code standard qu'il faut écrire correctement. Se souvenir de la structure correcte peut être complexe, et l'écriture prend du temps. Pour simplifier considérablement cette tâche, vous pouvez utiliser Parasoft Jtest afin de générer automatiquement des tests paramétrés, comme ceux décrits précédemment. Pour ce faire, il suffit de sélectionner la méthode pour laquelle vous souhaitez générer un test (dans Eclipse ou IntelliJ) :

Capture d'écran créant un test paramétré dans Jtest

Le test est généré à l'aide des valeurs et des assertions par défaut. Vous pouvez ensuite configurer le test avec des valeurs d'entrée et des assertions réelles, et ajouter plus de lignes de données à la méthode data ().

Exécution du test paramétré

Parasoft Jtest peut exécuter des tests paramétrés directement dans Eclipse et IntelliJ.

Vue JUnit dans Jtest dans l'IDE Eclipse

La vue JUnit dans Eclipse

Notez que le nom de chaque test, comme indiqué, inclut les valeurs d'entrée de l'ensemble de données et les valeurs de résultat attendues. Cela peut faciliter le débogage du test en cas d'échec, car les paramètres d'entrée et les sorties attendues sont affichés pour chaque cas.

Vous pouvez également utiliser l'action Exécuter tout de Parasoft Jtest:

Vue arborescente dans Parasoft Jtest

La vue Flow Tree dans Parasoft Jtest

Il analyse le flux de test et fournit des informations détaillées sur le test précédent. Cela vous permet de voir ce qui s'est passé dans le test sans avoir besoin de réexécuter le test avec des points d'arrêt ou des instructions de débogage. Par exemple, vous pouvez voir les valeurs paramétrées dans la vue Variables:

Vue des variables dans Parasoft Jtest

La vue des variables dans Parasoft Jtest

Conclusion

Chacun des trois frameworks que nous avons examinés est un excellent choix et fonctionne parfaitement. Si vous utilisez JUnit 4, je préfère JunitParams au framework paramétré intégré de JUnit 4, grâce à la conception plus claire des classes de test et à la possibilité de définir plusieurs méthodes de test dans une même classe. En revanche, si vous utilisez JUnit 5, je recommande le framework intégré de JUnit 5, car il corrige les lacunes de JUnit 4 et ne nécessite aucune bibliothèque supplémentaire. J'apprécie également l'utilisation des fonctionnalités de tests unitaires de Parasoft, qui rendent la création, l'exécution et le débogage des tests paramétrés plus efficaces.

Découvrez comment Parasoft Jtest peut vous aider à améliorer la qualité de votre code Java et la productivité de votre équipe.

Démonstration de la plateforme