Guida alle regole di JUnit 4

1. Panoramica

In questo tutorial, daremo un'occhiata alla funzione Regole fornita dalla libreria JUnit 4.

Inizieremo introducendo il modello di regole JUnit prima di esaminare le regole di base più importanti fornite dalla distribuzione. Inoltre, vedremo anche come scrivere e utilizzare la nostra regola JUnit personalizzata.

Per saperne di più sui test con JUnit, controlla la nostra serie completa di JUnit.

Nota che se stai usando JUnit 5, le regole sono state sostituite dal modello Extension.

2. Introduzione alle regole di JUnit 4

Le regole di JUnit 4 forniscono un meccanismo flessibile per migliorare i test eseguendo del codice attorno all'esecuzione di uno scenario di test . In un certo senso, è simile ad avere le annotazioni @Before e @After nella nostra classe di test.

Immaginiamo di voler connetterci a una risorsa esterna come un database durante la configurazione del test e quindi chiudere la connessione al termine del nostro test. Se vogliamo usare quel database in più test, finiremo per duplicare quel codice in ogni test.

Utilizzando una regola, possiamo avere tutto isolato in un unico posto e riutilizzare facilmente il codice da più classi di test.

3. Utilizzo delle regole di JUnit 4

Allora come possiamo usare le regole? Possiamo usare le regole di JUnit 4 seguendo questi semplici passaggi:

  • Aggiungi un campo pubblico alla nostra classe di test e assicurati che il tipo di questo campo sia un sottotipo dell'interfaccia org.junit.rules.TestRule
  • Annota il campo con l' annotazione @Rule

Nella sezione successiva, vedremo quali dipendenze del progetto abbiamo bisogno per iniziare.

4. Dipendenze di Maven

Innanzitutto, aggiungiamo le dipendenze del progetto di cui avremo bisogno per i nostri esempi. Avremo solo bisogno della libreria principale di JUnit 4:

 junit junit 4.12  

Come sempre, possiamo ottenere l'ultima versione da Maven Central.

5. Regole fornite nella distribuzione

Naturalmente, JUnit fornisce una serie di regole utili e predefinite come parte della libreria . Possiamo trovare tutte queste regole nel pacchetto org.junit.rules .

In questa sezione vedremo alcuni esempi di come utilizzarli.

5.1. La regola TemporaryFolder

Durante il test, spesso è necessario accedere a un file o una cartella temporanei. Tuttavia, la gestione della creazione e dell'eliminazione di questi file può essere complicata. Utilizzando la regola TemporaryFolder , possiamo gestire la creazione di file e cartelle che dovrebbero essere eliminati quando il metodo di test termina :

@Rule public TemporaryFolder tmpFolder = new TemporaryFolder(); @Test public void givenTempFolderRule_whenNewFile_thenFileIsCreated() throws IOException { File testFile = tmpFolder.newFile("test-file.txt"); assertTrue("The file should have been created: ", testFile.isFile()); assertEquals("Temp folder and test file should match: ", tmpFolder.getRoot(), testFile.getParentFile()); }

Come possiamo vedere, definiamo prima la regola TemporaryFolder tmpFolder . Successivamente, il nostro metodo di test crea un file chiamato test-file.txt nella cartella temporanea. Quindi controlliamo che il file sia stato creato e che esista dove dovrebbe. Veramente carino e semplice!

Al termine del test, la cartella e il file temporanei dovrebbero essere eliminati. Tuttavia, questa regola non controlla se l'eliminazione è riuscita o meno.

Ci sono anche alcuni altri metodi interessanti che vale la pena menzionare in questa classe:

  • newFile()

    Se non forniamo alcun nome di file, questo metodo crea un nuovo file con nome casuale.

  • newFolder(String... folderNames)

    Per creare cartelle temporanee ricorsivamente profonde, possiamo utilizzare questo metodo.

  • newFolder()

    Allo stesso modo, il metodo newFolder () crea una nuova cartella con nome casuale.

Una bella aggiunta degna di nota è che a partire dalla versione 4.13, la regola TemporaryFolder consente la verifica delle risorse eliminate:

@Rule public TemporaryFolder folder = TemporaryFolder.builder().assureDeletion().build();

Se una risorsa non può essere eliminata, il test fallisce con un AssertionError .

Infine, in JUnit 5, possiamo ottenere la stessa funzionalità utilizzando l'estensione Directory temporanea.

5.2. La regola ExpectedException

Come suggerisce il nome, possiamo utilizzare la regola ExpectedException per verificare che alcuni codici generino un'eccezione prevista:

@Rule public final ExpectedException thrown = ExpectedException.none(); @Test public void givenIllegalArgument_whenExceptionThrown_MessageAndCauseMatches() { thrown.expect(IllegalArgumentException.class); thrown.expectCause(isA(NullPointerException.class)); thrown.expectMessage("This is illegal"); throw new IllegalArgumentException("This is illegal", new NullPointerException()); }

Come possiamo vedere nell'esempio sopra, dichiariamo prima la regola ExpectedException . Quindi, nel nostro test, stiamo affermando che viene generata un'eccezione IllegalArgumentException .

Utilizzando questa regola, possiamo anche verificare alcune altre proprietà dell'eccezione, come il messaggio e la causa.

Per una guida approfondita al test delle eccezioni con JUnit, consulta la nostra eccellente guida su come affermare un'eccezione.

5.3. La regola TestName

In parole povere, la regola TestName fornisce il nome del test corrente all'interno di un determinato metodo di test:

@Rule public TestName name = new TestName(); @Test public void givenAddition_whenPrintingTestName_thenTestNameIsDisplayed() { LOG.info("Executing: {}", name.getMethodName()); assertEquals("givenAddition_whenPrintingTestName_thenTestNameIsDisplayed", name.getMethodName()); }

In questo banale esempio, quando eseguiamo lo unit test, dovremmo vedere il nome del test nell'output:

INFO c.baeldung.rules.JUnitRulesUnitTest - Executing: givenAddition_whenPrintingTestName_thenTestNameIsDisplayed

5.4. La regola del timeout

In questo prossimo esempio, daremo un'occhiata alla regola Timeout . Questa regola offre un'utile alternativa all'utilizzo del parametro timeout su una singola annotazione Test .

Vediamo ora come utilizzare questa regola per impostare un timeout globale su tutti i metodi di test nella nostra classe di test:

@Rule public Timeout globalTimeout = Timeout.seconds(10); @Test public void givenLongRunningTest_whenTimout_thenTestFails() throws InterruptedException { TimeUnit.SECONDS.sleep(20); }

Nell'esempio banale sopra, definiamo prima un timeout globale per tutti i metodi di test di 10 secondi . Quindi definiamo deliberatamente un test che richiederà più di 10 secondi.

Quando eseguiamo questo test, dovremmo vedere un errore di test:

org.junit.runners.model.TestTimedOutException: test timed out after 10 seconds ...

5.5. La regola ErrorCollector

Successivamente daremo un'occhiata alla regola ErrorCollector . Questa regola consente di continuare l'esecuzione di un test dopo che è stato rilevato il primo problema .

Vediamo come possiamo utilizzare questa regola per raccogliere tutti gli errori e segnalarli tutti in una volta al termine del test:

@Rule public final ErrorCollector errorCollector = new ErrorCollector(); @Test public void givenMultipleErrors_whenTestRuns_thenCollectorReportsErrors() { errorCollector.addError(new Throwable("First thing went wrong!")); errorCollector.addError(new Throwable("Another thing went wrong!")); errorCollector.checkThat("Hello World", not(containsString("ERROR!"))); }

Nell'esempio sopra, aggiungiamo due errori al raccoglitore. Quando eseguiamo il test, l'esecuzione continua, ma alla fine il test fallirà.

Nell'output vedremo entrambi gli errori riportati:

java.lang.Throwable: First thing went wrong! ... java.lang.Throwable: Another thing went wrong!

5.6. La regola del verificatore

The Verifier rule is an abstract base class that we can use when we wish to verify some additional behavior from our tests. In fact, the ErrorCollector rule we saw in the last section extends this class.

Let's now take a look at a trivial example of defining our own verifier:

private List messageLog = new ArrayList(); @Rule public Verifier verifier = new Verifier() { @Override public void verify() { assertFalse("Message Log is not Empty!", messageLog.isEmpty()); } }; 

Here, we define a new Verifier and override the verify() method to add some extra verification logic. In this straightforward example, we simply check to see that the message log in our example isn't empty.

Now, when we run the unit test and add a message, we should see that our verifier has been applied:

@Test public void givenNewMessage_whenVerified_thenMessageLogNotEmpty() { // ... messageLog.add("There is a new message!"); }

5.7. The DisableOnDebug Rule

Sometimes we may want to disable a rule when we're debugging. For example, it’s often desirable to disable a Timeout rule when debugging to avoid our test timing out and failing before we've had time to debug it properly.

The DisableOnDebug Rule does precisely this and allows us to label certain rules to be disabled when debugging:

@Rule public DisableOnDebug disableTimeout = new DisableOnDebug(Timeout.seconds(30));

In the example above we can see that in order to use this rule, we simply pass the rule we want to disable to the constructor.

The main benefit of this rule is that we can disable rules without making any modifications to our test classes during debugging.

5.8. The ExternalResource Rule

Typically, when writing integration tests, we may wish to set up an external resource before a test and tear it down afterward. Thankfully, JUnit provides another handy base class for this.

We can extend the abstract class ExternalResource to set up an external resource before a test, such as a file or a database connection. In fact, the TemporaryFolder rule we saw earlier extends ExternalResource.

Let's take a quick look at how we could extend this class:

@Rule public final ExternalResource externalResource = new ExternalResource() { @Override protected void before() throws Throwable { // code to set up a specific external resource. }; @Override protected void after() { // code to tear down the external resource }; };

In this example, when we define an external resource we simply need to override the before() method and after() method in order to set up and tear down our external resource.

6. Applying Class Rules

Up until now, all the examples we've looked at have applied to single test case methods. However, sometimes we might want to apply a rule at the test class level. We can accomplish this by using the @ClassRule annotation.

This annotation works very similarly to @Rule but wraps a rule around a whole test — the main difference being that the field we use for our class rule must be static:

@ClassRule public static TemporaryFolder globalFolder = new TemporaryFolder();

7. Defining a Custom JUnit Rule

As we've seen, JUnit 4 provides a number of useful rules out of the box. Of course, we can define our own custom rules. To write a custom rule, we need to implement the TestRule interface.

Let's take a look at an example of defining a custom test method name logger rule:

public class TestMethodNameLogger implements TestRule { private static final Logger LOG = LoggerFactory.getLogger(TestMethodNameLogger.class); @Override public Statement apply(Statement base, Description description) { logInfo("Before test", description); try { return new Statement() { @Override public void evaluate() throws Throwable { base.evaluate(); } }; } finally { logInfo("After test", description); } } private void logInfo(String msg, Description description) { LOG.info(msg + description.getMethodName()); } }

As we can see, the TestRule interface contains one method called apply(Statement, Description) that we must override to return an instance of Statement. The statement represents our tests within the JUnit runtime. When we call the evaluate() method, this executes our test.

In this example, we log a before and after message and include from the Description object the method name of the individual test.

8. Using Rule Chains

In this final section, we'll take a look at how we can order several test rules using the RuleChain rule:

@Rule public RuleChain chain = RuleChain.outerRule(new MessageLogger("First rule")) .around(new MessageLogger("Second rule")) .around(new MessageLogger("Third rule"));

Nell'esempio sopra, creiamo una catena di tre regole che stampano semplicemente il messaggio passato a ciascun costruttore di MessageLogger .

Quando eseguiremo il nostro test, vedremo come viene applicata la catena in ordine:

Starting: First rule Starting: Second rule Starting: Third rule Finished: Third rule Finished: Second rule Finished: First rule

9. Conclusione

Per riassumere, in questo tutorial, abbiamo esplorato in dettaglio le regole di JUnit 4.

Per prima cosa, abbiamo iniziato spiegando cosa sono le regole e come possiamo usarle. Successivamente, abbiamo esaminato in modo approfondito le regole che fanno parte della distribuzione JUnit.

Infine, abbiamo esaminato come definire la nostra regola personalizzata e come concatenare le regole insieme.

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