Guide de la bibliothèque de règles système

1. Vue d'ensemble

Parfois, lors de l'écriture de tests unitaires, nous pouvons avoir besoin de tester du code qui interagit directement avec la classe System . Généralement dans les applications telles que les outils de ligne de commande qui appellent directement System.exit ou lisent des arguments à l'aide de System.in .

Dans ce didacticiel, nous examinerons les fonctionnalités les plus courantes d'une bibliothèque externe soignée appelée System Rules, qui fournit un ensemble de règles JUnit pour tester le code utilisant la classe System .

2. Dépendances de Maven

Tout d'abord, ajoutons la dépendance des règles système à notre pom.xml :

 com.github.stefanbirkner system-rules 1.19.0 

Nous ajouterons également la dépendance System Lambda qui est également disponible à partir de Maven Central:

 com.github.stefanbirkner system-lambda 1.1.0 

Comme les règles système ne prennent pas directement en charge JUnit5 , nous avons ajouté la dernière dépendance. Cela fournit les méthodes de wrapper System Lambda à utiliser dans les tests. Il existe une alternative basée sur des extensions à cela appelée Stubs système.

3. Utilisation des propriétés système

Pour récapituler rapidement, la plate-forme Java utilise un objet Properties pour fournir des informations sur le système local et la configuration. Nous pouvons facilement imprimer les propriétés:

System.getProperties() .forEach((key, value) -> System.out.println(key + ": " + value));

Comme nous pouvons le voir, les propriétés incluent des informations telles que l'utilisateur actuel, la version actuelle du runtime Java et le séparateur de nom de chemin de fichier:

java.version: 1.8.0_221 file.separator: / user.home: /Users/baeldung os.name: Mac OS X ...

Nous pouvons également définir nos propres propriétés système à l'aide de la méthode System.setProperty . Des précautions doivent être prises lors de l'utilisation des propriétés système de nos tests, car ces propriétés sont globales JVM.

Par exemple, si nous définissons une propriété système, nous devons nous assurer de restaurer la propriété à sa valeur d'origine lorsque notre test se termine ou en cas d'échec. Cela peut parfois entraîner une configuration fastidieuse et un code de suppression. Cependant, si nous négligeons de le faire, cela pourrait entraîner des effets secondaires inattendus dans nos tests.

Dans la section suivante, nous verrons comment nous pouvons fournir, nettoyer et nous assurer de restaurer les valeurs des propriétés système une fois nos tests terminés de manière concise et simple.

4. Fourniture des propriétés système

Imaginons que nous ayons une propriété système log_dir qui contient l'emplacement où nos journaux doivent être écrits et que notre application définit cet emplacement au démarrage:

System.setProperty("log_dir", "/tmp/baeldung/logs");

4.1. Fournir une propriété unique

Considérons maintenant qu'à partir de notre test unitaire, nous voulons fournir une valeur différente. Nous pouvons le faire en utilisant la règle provideSystemProperty :

public class ProvidesSystemPropertyWithRuleUnitTest { @Rule public final ProvideSystemProperty providesSystemPropertyRule = new ProvideSystemProperty("log_dir", "test/resources"); @Test public void givenProvideSystemProperty_whenGetLogDir_thenLogDirIsProvidedSuccessfully() { assertEquals("log_dir should be provided", "test/resources", System.getProperty("log_dir")); } // unit test definition continues } 

À l'aide de la règle ProvideSystemProperty , nous pouvons définir une valeur arbitraire pour une propriété système donnée à utiliser à partir de nos tests. Dans cet exemple, nous définissons la propriété log_dir sur notre répertoire test / resources et, à partir de notre test unitaire, affirmons simplement que la valeur de la propriété test a été fournie avec succès.

Si nous imprimons ensuite la valeur de la propriété log_dir lorsque notre classe de test se termine:

@AfterClass public static void tearDownAfterClass() throws Exception { System.out.println(System.getProperty("log_dir")); } 

Nous pouvons voir que la valeur de la propriété a été restaurée à sa valeur d'origine:

/tmp/baeldung/logs

4.2. Fournir plusieurs propriétés

Si nous devons fournir plusieurs propriétés, nous pouvons utiliser la méthode and pour enchaîner autant de valeurs de propriété que nécessaire pour notre test:

@Rule public final ProvideSystemProperty providesSystemPropertyRule = new ProvideSystemProperty("log_dir", "test/resources").and("another_property", "another_value")

4.3. Fournir des propriétés à partir d'un fichier

De même, nous avons également la possibilité de fournir des propriétés à partir d'un fichier ou d'une ressource de chemin de classe en utilisant la règle FournirSystemProperty :

@Rule public final ProvideSystemProperty providesSystemPropertyFromFileRule = ProvideSystemProperty.fromResource("/test.properties"); @Test public void givenProvideSystemPropertyFromFile_whenGetName_thenNameIsProvidedSuccessfully() { assertEquals("name should be provided", "baeldung", System.getProperty("name")); assertEquals("version should be provided", "1.0", System.getProperty("version")); }

Dans l'exemple ci-dessus, nous supposons que nous avons un fichier test.properties sur le chemin de classe :

name=baeldung version=1.0

4.4. Fournir des propriétés avec JUnit5 et Lambdas

Comme nous l'avons mentionné précédemment, nous pourrions également utiliser la version System Lambda de la bibliothèque pour implémenter des tests compatibles avec JUnit5.

Voyons comment implémenter notre test en utilisant cette version de la bibliothèque:

@BeforeAll static void setUpBeforeClass() throws Exception { System.setProperty("log_dir", "/tmp/baeldung/logs"); } @Test void givenSetSystemProperty_whenGetLogDir_thenLogDirIsProvidedSuccessfully() throws Exception { restoreSystemProperties(() -> { System.setProperty("log_dir", "test/resources"); assertEquals("log_dir should be provided", "test/resources", System.getProperty("log_dir")); }); assertEquals("log_dir should be provided", "/tmp/baeldung/logs", System.getProperty("log_dir")); }

Dans cette version, nous pouvons utiliser la méthode restoreSystemProperties pour exécuter une instruction donnée. Dans cette instruction, nous pouvons configurer et fournir les valeurs dont nous avons besoin pour nos propriétés système . Comme nous pouvons le voir après la fin de l'exécution de cette méthode, la valeur de log_dir est la même qu'avant / tmp / baeldung / logs .

Malheureusement, il n'y a pas de prise en charge intégrée pour fournir des propriétés à partir de fichiers à l'aide de la méthode restoreSystemProperties .

5. Effacement des propriétés du système

Parfois, nous pouvons souhaiter effacer un ensemble de propriétés système lorsque notre test démarre et restaurer leurs valeurs d'origine à la fin du test, qu'il réussisse ou échoue.

Nous pouvons utiliser la règle ClearSystemProperties à cet effet:

@Rule public final ClearSystemProperties userNameIsClearedRule = new ClearSystemProperties("user.name"); @Test public void givenClearUsernameProperty_whenGetUserName_thenNull() { assertNull(System.getProperty("user.name")); }

La propriété système user.name est l'une des propriétés système prédéfinies, qui contient le nom du compte utilisateur. Comme prévu dans le test unitaire ci-dessus, nous effaçons cette propriété et vérifions qu'elle est vide de notre test.

De manière pratique, nous pouvons également transmettre plusieurs noms de propriétés au constructeur ClearSystemProperties .

6. Mocking System.in

De temps en temps, nous pouvons créer des applications de ligne de commande interactives qui lisent à partir de System.in .

Pour cette section, nous utiliserons un exemple très simple qui lit un prénom et un nom à partir de l'entrée standard et les concatène ensemble:

private String getFullname() { try (Scanner scanner = new Scanner(System.in)) { String firstName = scanner.next(); String surname = scanner.next(); return String.join(" ", firstName, surname); } }

Les règles système contiennent la règle TextFromStandardInputStream que nous pouvons utiliser pour spécifier les lignes à fournir lors de l'appel de System.in :

@Rule public final TextFromStandardInputStream systemInMock = emptyStandardInputStream(); @Test public void givenTwoNames_whenSystemInMock_thenNamesJoinedTogether() { systemInMock.provideLines("Jonathan", "Cook"); assertEquals("Names should be concatenated", "Jonathan Cook", getFullname()); }

Pour ce faire, nous utilisons la méthode providesLines , qui prend un paramètre varargs pour permettre de spécifier plusieurs valeurs.

Dans cet exemple, nous fournissons deux valeurs avant d'appeler la méthode getFullname , où System.in est référencé. Nos deux valeurs de ligne fournies seront renvoyées chaque fois que nous appelons scanner.next () .

Voyons comment nous pouvons obtenir la même chose dans une version JUnit 5 du test à l'aide de System Lambda:

@Test void givenTwoNames_whenSystemInMock_thenNamesJoinedTogether() throws Exception { withTextFromSystemIn("Jonathan", "Cook").execute(() -> { assertEquals("Names should be concatenated", "Jonathan Cook", getFullname()); }); }

Dans cette variante, nous utilisons la méthode withTextFromSystemIn nommée de manière similaire , qui nous permet de spécifier les valeurs System.in fournies .

Il est important de mentionner dans les deux cas qu'après la fin du test, la valeur d'origine de System.in sera restaurée.

7. Test de System.out et System.err

Dans un didacticiel précédent, nous avons vu comment utiliser les règles système pour tester les unités System.out.println ().

De manière pratique, nous pouvons appliquer une approche presque identique pour tester le code qui interagit avec le flux d'erreur standard. Cette fois, nous utilisons la SystemErrRule :

@Rule public final SystemErrRule systemErrRule = new SystemErrRule().enableLog(); @Test public void givenSystemErrRule_whenInvokePrintln_thenLogSuccess() { printError("An Error occurred Baeldung Readers!!"); Assert.assertEquals("An Error occurred Baeldung Readers!!", systemErrRule.getLog().trim()); } private void printError(String output) { System.err.println(output); }

Nice! Using the SystemErrRule, we can intercept the writes to System.err. First, we start logging everything written to System.err by calling the enableLog method on our rule. Then we simply call getLog to get the text written to System.err since we called enableLog.

Now, let's implement the JUnit5 version of our test:

@Test void givenTapSystemErr_whenInvokePrintln_thenOutputIsReturnedSuccessfully() throws Exception { String text = tapSystemErr(() -> { printError("An error occurred Baeldung Readers!!"); }); Assert.assertEquals("An error occurred Baeldung Readers!!", text.trim()); }

In this version, we make use of the tapSystemErr method, which executes the statement and lets us capture the content passed to System.err.

8. Handling System.exit

Command-line applications typically terminate by calling System.exit. If we want to test such an application, it is likely that our test will terminate abnormally before it finishes when it encounters the code which calls System.exit.

Thankfully, System Rules provides a neat solution to handle this using the ExpectedSystemExit rule:

@Rule public final ExpectedSystemExit exitRule = ExpectedSystemExit.none(); @Test public void givenSystemExitRule_whenAppCallsSystemExit_thenExitRuleWorkssAsExpected() { exitRule.expectSystemExitWithStatus(1); exit(); } private void exit() { System.exit(1); }

Using the ExpectedSystemExit rule allows us to specify from our test the expected System.exit() call. In this simple example, we also check the expected status code using the expectSystemExitWithStatus method.

We can achieve something similar in our JUnit 5 version using the catchSystemExit method:

@Test void givenCatchSystemExit_whenAppCallsSystemExit_thenStatusIsReturnedSuccessfully() throws Exception { int statusCode = catchSystemExit(() -> { exit(); }); assertEquals("status code should be 1:", 1, statusCode); }

9. Conclusion

Pour résumer, dans ce didacticiel, nous avons exploré en détail la bibliothèque de règles système.

Tout d'abord, nous avons commencé par expliquer comment tester le code qui utilise les propriétés système. Ensuite, nous avons regardé comment tester la sortie standard et l'entrée standard. Enfin, nous avons regardé comment gérer le code qui appelle System.exit à partir de nos tests.

La bibliothèque de règles système prend également en charge la fourniture de variables d'environnement et de gestionnaires de sécurité spéciaux issus de nos tests . Assurez-vous de consulter la documentation complète pour plus de détails.

Comme toujours, le code source complet de l'article est disponible à l'adresse over sur GitHub.