Guida alla libreria delle regole di sistema

1. Panoramica

A volte, durante la scrittura di unit test, potrebbe essere necessario testare codice che interagisce direttamente con la classe System . In genere in applicazioni come strumenti della riga di comando che chiamano direttamente System.exit o leggono argomenti utilizzando System.in .

In questo tutorial, daremo uno sguardo alle funzionalità più comuni di una libreria esterna ordinata chiamata Regole di sistema che fornisce un insieme di regole JUnit per il test del codice che utilizza la classe System .

2. Dipendenze di Maven

Per prima cosa, aggiungiamo la dipendenza dalle regole di sistema al nostro pom.xml :

 com.github.stefanbirkner system-rules 1.19.0 

Aggiungeremo anche la dipendenza System Lambda, disponibile anche da Maven Central:

 com.github.stefanbirkner system-lambda 1.1.0 

Poiché le regole di sistema non supportano direttamente JUnit5 , abbiamo aggiunto l'ultima dipendenza. Fornisce i metodi wrapper System Lambda da utilizzare nei test. Esiste un'alternativa basata su estensioni a questa chiamata System Stubs.

3. Lavorare con le proprietà del sistema

Per ricapitolare rapidamente, la piattaforma Java utilizza un oggetto Properties per fornire informazioni sul sistema e sulla configurazione locali. Possiamo facilmente stampare le proprietà:

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

Come possiamo vedere, le proprietà includono informazioni come l'utente corrente, la versione corrente del runtime Java e il separatore del nome del percorso del file:

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

Possiamo anche impostare le nostre proprietà di sistema utilizzando il metodo System.setProperty . È necessario prestare attenzione quando si lavora con le proprietà di sistema dai nostri test, poiché queste proprietà sono globali JVM.

Ad esempio, se impostiamo una proprietà di sistema, dovremmo assicurarci di ripristinare la proprietà al suo valore originale al termine del nostro test o se si verifica un errore. Questo a volte può portare a configurazioni ingombranti e abbattere il codice. Tuttavia, se trascuriamo di farlo, potrebbe portare a effetti collaterali inaspettati nei nostri test.

Nella sezione successiva, vedremo come possiamo fornire, pulire e assicurarci di ripristinare i valori delle proprietà di sistema dopo il completamento dei nostri test in modo conciso e semplice.

4. Fornire le proprietà di sistema

Immaginiamo di avere una proprietà di sistema log_dir che contiene la posizione in cui devono essere scritti i nostri log e la nostra applicazione imposta questa posizione all'avvio:

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

4.1. Fornisci una singola proprietà

Ora consideriamo che dal nostro unit test, vogliamo fornire un valore diverso. Possiamo farlo usando la regola 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 } 

Utilizzando la regola ProvideSystemProperty , possiamo impostare un valore arbitrario per una determinata proprietà di sistema da utilizzare dai nostri test. In questo esempio, impostiamo la proprietà log_dir sulla nostra directory test / resources e dal nostro unit test, asseriamo semplicemente che il valore della proprietà di test è stato fornito con successo.

Se poi stampiamo il valore della proprietà log_dir al termine della nostra classe di test:

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

Possiamo vedere che il valore della proprietà è stato riportato al suo valore originale:

/tmp/baeldung/logs

4.2. Fornire più proprietà

Se dobbiamo fornire più proprietà, possiamo utilizzare il metodo e per concatenare tutti i valori di proprietà necessari per il nostro test:

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

4.3. Fornire proprietà da un file

Allo stesso modo, abbiamo anche la possibilità di fornire proprietà da un file o una risorsa del percorso di classe utilizzando la regola ProvideSystemProperty :

@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")); }

Nell'esempio sopra, assumiamo di avere un file test.properties sul classpath:

name=baeldung version=1.0

4.4. Fornitura di proprietà con JUnit5 e Lambda

Come accennato in precedenza, potremmo anche utilizzare la versione System Lambda della libreria per implementare test compatibili con JUnit5.

Vediamo come implementare il nostro test utilizzando questa versione della libreria:

@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")); }

In questa versione, possiamo utilizzare il metodo restoreSystemProperties per eseguire una determinata istruzione. All'interno di questa istruzione, possiamo impostare e fornire i valori richiesti per le nostre proprietà di sistema . Come possiamo vedere dopo che questo metodo termina l'esecuzione, il valore di log_dir è lo stesso di prima / tmp / baeldung / logs .

Sfortunatamente, non esiste un supporto integrato per fornire proprietà dai file utilizzando il metodo restoreSystemProperties .

5. Cancellazione delle proprietà del sistema

A volte potremmo voler cancellare una serie di proprietà di sistema all'avvio del nostro test e ripristinare i valori originali al termine del test, indipendentemente dal fatto che abbia successo o meno.

Possiamo usare la regola ClearSystemProperties per questo scopo:

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

La proprietà di sistema user.name è una delle proprietà di sistema predefinite, che contiene il nome dell'account utente. Come previsto nello unit test di cui sopra, cancelliamo questa proprietà e controlliamo che sia vuota dal nostro test.

Convenientemente, possiamo anche passare più nomi di proprietà al costruttore ClearSystemProperties .

6. Mocking System.in

Di tanto in tanto, potremmo creare applicazioni a riga di comando interattive che leggono da System.in .

Per questa sezione, utilizzeremo un esempio molto semplice che legge un nome e un cognome dall'input standard e li concatena insieme:

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

System Rules contains the TextFromStandardInputStream rule which we can use to specify the lines that should be provided when calling 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()); }

We accomplish this by using the providesLines method, which takes a varargs parameter to enable specifying more than one value.

In this example, we provide two values before calling the getFullname method, where System.in is referenced. Our two provided line values will be returned each time we call scanner.next().

Let's take a look at how we can achieve the same in a JUnit 5 version of the test using System Lambda:

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

In this variation, we use the similarly named withTextFromSystemIn method, which lets us specify the provided System.in values.

It is important to mention in both cases that after the test finishes, the original value of System.in will be restored.

7. Testing System.out and System.err

In a previous tutorial, we saw how to use System Rules to unit test System.out.println().

Conveniently, we can apply an almost identical approach for testing code which interacts with the standard error stream. This time we use the 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. Conclusione

Per riassumere, in questo tutorial, abbiamo esplorato in dettaglio la libreria delle regole di sistema.

Innanzitutto, abbiamo iniziato spiegando come testare il codice che utilizza le proprietà di sistema. Quindi abbiamo esaminato come testare lo standard output e lo standard input. Infine, abbiamo esaminato come gestire il codice che chiama System.exit dai nostri test.

La libreria delle regole di sistema fornisce anche il supporto per fornire variabili di ambiente e gestori di sicurezza speciali dai nostri test . Assicurati di controllare la documentazione completa per i dettagli.

Come sempre, il codice sorgente completo dell'articolo è disponibile su GitHub.