Una guida a JUnit 5

1. Panoramica

JUnit è uno dei framework di unit test più popolari nell'ecosistema Java. La versione JUnit 5 contiene una serie di innovazioni entusiasmanti, con l'obiettivo di supportare nuove funzionalità in Java 8 e versioni successive , oltre a consentire molti stili diversi di test.

2. Dipendenze di Maven

L'impostazione di JUnit 5.x.0 è piuttosto semplice, dobbiamo aggiungere la seguente dipendenza al nostro pom.xml :

 org.junit.jupiter junit-jupiter-engine 5.1.0 test 

È importante notare che questa versione richiede Java 8 per funzionare .

Inoltre, ora è disponibile il supporto diretto per eseguire gli unit test sulla piattaforma JUnit in Eclipse e IntelliJ. Ovviamente puoi anche eseguire test utilizzando l'obiettivo Maven Test.

D'altra parte, IntelliJ supporta JUnit 5 per impostazione predefinita. Pertanto, eseguire JUnit 5 su IntelliJ è piuttosto semplice, basta fare clic con il pulsante destro del mouse -> Esegui o Ctrl-Maiusc-F10.

3. Architettura

JUnit 5 è composto da diversi moduli diversi da tre diversi sottoprogetti:

3.1. Piattaforma JUnit

La piattaforma è responsabile del lancio di framework di test sulla JVM. Definisce un'interfaccia stabile e potente tra JUnit e il suo client come gli strumenti di compilazione.

L'obiettivo finale è come i suoi clienti si integrino facilmente con JUnit nella scoperta e nell'esecuzione dei test.

Definisce inoltre l'API TestEngine per lo sviluppo di un framework di test che viene eseguito sulla piattaforma JUnit. In questo modo, puoi collegare librerie di test di terze parti, direttamente in JUnit, implementando TestEngine personalizzato.

3.2. JUnit Jupiter

Questo modulo include nuovi modelli di programmazione ed estensione per la scrittura di test in JUnit 5. Nuove annotazioni rispetto a JUnit 4 sono:

  • @TestFactory - denota un metodo che è una fabbrica di test per test dinamici
  • @DisplayName : definisce il nome visualizzato personalizzato per una classe di test o un metodo di test
  • @Nested - denota che la classe annotata è una classe di test nidificata e non statica
  • @Tag : dichiara i tag per filtrare i test
  • @ExtendWith : viene utilizzato per registrare estensioni personalizzate
  • @BeforeEach - denota che il metodo annotato verrà eseguito prima di ogni metodo di test (in precedenza @Before )
  • @AfterEach - denota che il metodo annotato verrà eseguito dopo ogni metodo di test (in precedenza @After )
  • @BeforeAll - denota che il metodo annotato verrà eseguito prima di tutti i metodi di test nella classe corrente (in precedenza @BeforeClass )
  • @AfterAll - denota che il metodo annotato verrà eseguito dopo tutti i metodi di test nella classe corrente (in precedenza @AfterClass )
  • @Disable : viene utilizzato per disabilitare una classe o un metodo di test (in precedenza @Ignore )

3.3. JUnit Vintage

Supporta l'esecuzione di test basati su JUnit 3 e JUnit 4 sulla piattaforma JUnit 5.

4. Annotazioni di base

Per discutere nuove annotazioni, abbiamo suddiviso la sezione nei seguenti gruppi, responsabili dell'esecuzione: prima dei test, durante i test (opzionale) e dopo i test:

4.1. @BeforeAll e @BeforeEach

Di seguito è riportato un esempio del semplice codice da eseguire prima dei principali casi di test:

@BeforeAll static void setup() { log.info("@BeforeAll - executes once before all test methods in this class"); } @BeforeEach void init() { log.info("@BeforeEach - executes before each test method in this class"); }

È importante notare che il metodo con l' annotazione @BeforeAll deve essere statico, altrimenti il ​​codice non verrà compilato.

4.2. @DisplayName e @Disabled

Passiamo a nuovi metodi facoltativi di prova:

@DisplayName("Single test successful") @Test void testSingleSuccessTest() { log.info("Success"); } @Test @Disabled("Not implemented yet") void testShowSomething() { }

Come possiamo vedere, possiamo cambiare il nome visualizzato o disabilitare il metodo con un commento, utilizzando nuove annotazioni.

4.3. @AfterEach e @AfterAll

Infine, discutiamo i metodi collegati alle operazioni dopo l'esecuzione dei test:

@AfterEach void tearDown() { log.info("@AfterEach - executed after each test method."); } @AfterAll static void done() { log.info("@AfterAll - executed after all test methods."); }

Tieni presente che anche il metodo con @AfterAll deve essere un metodo statico.

5. Asserzioni e presupposti

JUnit 5 cerca di sfruttare appieno le nuove funzionalità di Java 8, in particolare le espressioni lambda.

5.1. Asserzioni

Le asserzioni sono state spostate in org.junit.jupiter.api.Assertions e sono state migliorate in modo significativo. Come accennato in precedenza, ora puoi usare lambda nelle asserzioni:

@Test void lambdaExpressions() { assertTrue(Stream.of(1, 2, 3) .stream() .mapToInt(i -> i) .sum() > 5, () -> "Sum should be greater than 5"); }

Sebbene l'esempio sopra sia banale, un vantaggio dell'utilizzo dell'espressione lambda per il messaggio di asserzione è che viene valutato pigramente, il che può far risparmiare tempo e risorse se la costruzione del messaggio è costosa.

It is also now possible to group assertions with assertAll() which will report any failed assertions within the group with a MultipleFailuresError:

 @Test void groupAssertions() { int[] numbers = {0, 1, 2, 3, 4}; assertAll("numbers", () -> assertEquals(numbers[0], 1), () -> assertEquals(numbers[3], 3), () -> assertEquals(numbers[4], 1) ); }

This means it is now safer to make more complex assertions, as you will be able to pinpoint the exact location of any failure.

5.2. Assumptions

Assumptions are used to run tests only if certain conditions are met. This is typically used for external conditions that are required for the test to run properly, but which are not directly related to whatever is being tested.

You can declare an assumption with assumeTrue(), assumeFalse(), and assumingThat().

@Test void trueAssumption() { assumeTrue(5 > 1); assertEquals(5 + 2, 7); } @Test void falseAssumption() { assumeFalse(5  assertEquals(2 + 2, 4) ); }

If an assumption fails, a TestAbortedException is thrown and the test is simply skipped.

Assumptions also understand lambda expressions.

6. Exception Testing

There are two ways of exception testing in JUnit 5. Both of them can be implemented by using assertThrows() method:

@Test void shouldThrowException() { Throwable exception = assertThrows(UnsupportedOperationException.class, () -> { throw new UnsupportedOperationException("Not supported"); }); assertEquals(exception.getMessage(), "Not supported"); } @Test void assertThrowsException() { String str = null; assertThrows(IllegalArgumentException.class, () -> { Integer.valueOf(str); }); }

The first example is used to verify more detail of the thrown exception and the second one just validates the type of exception.

7. Test Suites

To continue the new features of JUnit 5, we will try to get to know the concept of aggregating multiple test classes in a test suite so that we can run those together. JUnit 5 provides two annotations: @SelectPackages and @SelectClasses to create test suites.

Keep in mind that at this early stage most IDEs do not support those features.

Let's have a look at the first one:

@RunWith(JUnitPlatform.class) @SelectPackages("com.baeldung") public class AllUnitTest {}

@SelectPackage is used to specify the names of packages to be selected when running a test suite. In our example, it will run all test. The second annotation, @SelectClasses, is used to specify the classes to be selected when running a test suite:

@RunWith(JUnitPlatform.class) @SelectClasses({AssertionTest.class, AssumptionTest.class, ExceptionTest.class}) public class AllUnitTest {}

For example, above class will create a suite contains three test classes. Please note that the classes don't have to be in one single package.

8. Dynamic Tests

The last topic that we want to introduce is JUnit 5 Dynamic Tests feature, which allows to declare and run test cases generated at run-time. In contrary to the Static Tests which defines fixed number of test cases at the compile time, the Dynamic Tests allow us to define the tests case dynamically in the runtime.

Dynamic tests can be generated by a factory method annotated with @TestFactory. Let's have a look at the code example:

@TestFactory public Stream translateDynamicTestsFromStream() { return in.stream() .map(word -> DynamicTest.dynamicTest("Test translate " + word, () -> { int id = in.indexOf(word); assertEquals(out.get(id), translate(word)); }) ); }

This example is very straightforward and easy to understand. We want to translate words using two ArrayList, named in and out, respectively. The factory method must return a Stream, Collection, Iterable, or Iterator. In our case, we choose Java 8 Stream.

Please note that @TestFactory methods must not be private or static. The number of tests is dynamic, and it depends on the ArrayList size.

9. Conclusion

L'articolo era una rapida panoramica dei cambiamenti in arrivo con JUnit 5.

Possiamo vedere che JUnit 5 ha un grande cambiamento nella sua architettura che riguarda il launcher della piattaforma, l'integrazione con lo strumento di compilazione, IDE, altri framework di unit test, ecc. Inoltre, JUnit 5 è più integrato con Java 8, in particolare con i concetti Lambdas e Stream .

Gli esempi utilizzati in questo articolo sono disponibili nel progetto GitHub.