1. Información general
JUnit es uno de los marcos de pruebas unitarias más populares del ecosistema Java. La versión JUnit 5 contiene una serie de innovaciones interesantes, con el objetivo de admitir nuevas funciones en Java 8 y superior , además de permitir muchos estilos diferentes de pruebas.
2. Dependencias de Maven
Configurar JUnit 5.x.0 es bastante sencillo, necesitamos agregar la siguiente dependencia a nuestro pom.xml :
org.junit.jupiter junit-jupiter-engine 5.1.0 test
Es importante tener en cuenta que esta versión requiere Java 8 para funcionar .
Además, ahora hay soporte directo para ejecutar pruebas unitarias en la plataforma JUnit en Eclipse, así como en IntelliJ. Por supuesto, también puede ejecutar pruebas utilizando el objetivo de prueba de Maven.
Por otro lado, IntelliJ admite JUnit 5 de forma predeterminada. Por lo tanto, ejecutar JUnit 5 en IntelliJ es bastante simple, simplemente haga clic derecho -> Ejecutar, o Ctrl-Shift-F10.
3. Arquitectura
JUnit 5 se compone de varios módulos diferentes de tres subproyectos diferentes:
3.1. Plataforma JUnit
La plataforma es responsable de lanzar marcos de prueba en la JVM. Define una interfaz estable y potente entre JUnit y su cliente, como herramientas de compilación.
El objetivo final es cómo sus clientes se integran fácilmente con JUnit en el descubrimiento y ejecución de las pruebas.
También define la API TestEngine para desarrollar un marco de prueba que se ejecuta en la plataforma JUnit. Con eso, puede conectar bibliotecas de prueba de terceros, directamente en JUnit, implementando TestEngine personalizado.
3.2. JUnit Júpiter
Este módulo incluye nuevos modelos de programación y extensión para escribir pruebas en JUnit 5. Las nuevas anotaciones en comparación con JUnit 4 son:
- @TestFactory : denota un método que es una fábrica de pruebas para pruebas dinámicas
- @DisplayName : define el nombre de visualización personalizado para una clase de prueba o un método de prueba
- @Nested : denota que la clase anotada es una clase de prueba anidada y no estática
- @Tag : declara etiquetas para filtrar pruebas
- @ExtendWith : se usa para registrar extensiones personalizadas
- @BeforeEach: indica que el método anotado se ejecutará antes de cada método de prueba (anteriormente @Before )
- @AfterEach : indica que el método anotado se ejecutará después de cada método de prueba (anteriormente @After )
- @BeforeAll : indica que el método anotado se ejecutará antes que todos los métodos de prueba en la clase actual (anteriormente @BeforeClass )
- @AfterAll : indica que el método anotado se ejecutará después de todos los métodos de prueba en la clase actual (anteriormente @AfterClass )
- @Disable : se usa para deshabilitar una clase o método de prueba (anteriormente @Ignore )
3.3. JUnit Vintage
Admite la ejecución de pruebas basadas en JUnit 3 y JUnit 4 en la plataforma JUnit 5.
4. Anotaciones básicas
Para discutir nuevas anotaciones, dividimos la sección en los siguientes grupos, responsables de la ejecución: antes de las pruebas, durante las pruebas (opcional) y después de las pruebas:
4.1. @BeforeAll y @BeforeEach
A continuación se muestra un ejemplo del código simple que se ejecutará antes de los casos de prueba principales:
@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"); }
Es importante tener en cuenta que el método con la anotación @BeforeAll debe ser estático; de lo contrario, el código no se compilará.
4.2. @DisplayName y @Disabled
Pasemos a nuevos métodos opcionales de prueba:
@DisplayName("Single test successful") @Test void testSingleSuccessTest() { log.info("Success"); } @Test @Disabled("Not implemented yet") void testShowSomething() { }
Como podemos ver, podemos cambiar el nombre para mostrar o deshabilitar el método con un comentario, usando nuevas anotaciones.
4.3. @AfterEach y @AfterAll
Finalmente, analicemos los métodos conectados a las operaciones después de la ejecución de las pruebas:
@AfterEach void tearDown() { log.info("@AfterEach - executed after each test method."); } @AfterAll static void done() { log.info("@AfterAll - executed after all test methods."); }
Tenga en cuenta que el método con @AfterAll también debe ser un método estático.
5. Afirmaciones y suposiciones
JUnit 5 intenta aprovechar al máximo las nuevas características de Java 8, especialmente las expresiones lambda.
5.1. Afirmaciones
Las afirmaciones se han movido a org.junit.jupiter.api.Assertions y se han mejorado significativamente. Como se mencionó anteriormente, ahora puede usar lambdas en aserciones:
@Test void lambdaExpressions() { assertTrue(Stream.of(1, 2, 3) .stream() .mapToInt(i -> i) .sum() > 5, () -> "Sum should be greater than 5"); }
Aunque el ejemplo anterior es trivial, una ventaja de usar la expresión lambda para el mensaje de aserción es que se evalúa de manera perezosa, lo que puede ahorrar tiempo y recursos si la construcción del mensaje es 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
El artículo fue una descripción general rápida de los cambios que vienen con JUnit 5.
Podemos ver que JUnit 5 tiene un gran cambio en su arquitectura que se relaciona con el lanzador de plataforma, la integración con la herramienta de compilación, IDE, otros frameworks de pruebas unitarias, etc. Además, JUnit 5 está más integrado con Java 8, especialmente con los conceptos Lambdas y Stream. .
Los ejemplos utilizados en este artículo se pueden encontrar en el proyecto GitHub.