Personalización de la composición de las pruebas JUnit5 con application.properties

Imagine una situación en la que su proyecto necesita ser compilado en varios entornos.


Ahora imagine que no todas las pruebas deberían pasar en estos entornos, cada una tiene su propio conjunto de pruebas.


Y es preferible configurar la elección de qué pruebas deben realizarse en el archivo ... application.properties : cada prueba tiene su propio interruptor de encendido / apagado.


Suena genial, ¿no?


Entonces bienvenido al gato, donde implementamos todo esto usando SpringBoot 2 y JUnit 5.


Presets


Primero, apaguemos JUnit 4, que viene por defecto en SpringBoot 2, y encienda JUnit 5 .


Para hacer esto, realice cambios en pom.xml :


 <dependencies> <!--...--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>junit</groupId> <artifactId>junit</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.3.2</version> <scope>test</scope> </dependency> <!--...--> </dependencies> 

Solución propuesta


Queremos anotar cada prueba con una simple anotación con una propiedad que indique si la prueba está habilitada o no. Permítame recordarle que vamos a almacenar los valores de esta propiedad en el archivo application.properties .


Anotación


Crea una anotación :


 @Retention(RetentionPolicy.RUNTIME) @ExtendWith(TestEnabledCondition.class) public @interface TestEnabled { String property(); } 

Procesamiento de anotaciones


Un controlador de anotaciones es indispensable.


 public class TestEnabledCondition implements ExecutionCondition { @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { Optional<TestEnabled> annotation = context.getElement().map(e -> e.getAnnotation(TestEnabled.class)); return context.getElement() .map(e -> e.getAnnotation(TestEnabled.class)) .map(annotation -> { String property = annotation.property(); return Optional.ofNullable(environment.getProperty(property, Boolean.class)) .map(value -> { if (Boolean.TRUE.equals(value)) { return ConditionEvaluationResult.enabled("Enabled by property: "+property); } else { return ConditionEvaluationResult.disabled("Disabled by property: "+property); } }).orElse( ConditionEvaluationResult.disabled("Disabled - property <"+property+"> not set!") ); }).orElse( ConditionEvaluationResult.enabled("Enabled by default") ); } } 

Debe crear una clase (sin la anotación de Spring @Component ) que implemente la interfaz ExecutionCondition .


En esta clase, debe implementar un método de esta interfaz: ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) .


Este método toma el contexto de la prueba ejecutada por JUnit y devuelve la condición de si la prueba debe ejecutarse o no.


Lea más sobre la ejecución condicional de las pruebas JUnit5 en la documentación oficial.


Pero, ¿cómo verificamos el valor de la propiedad que está registrada en application.properties en este caso?


Obtener acceso al contexto Spring desde el contexto JUnit


De esta forma podemos obtener el entorno Spring con el que se ejecutó nuestra prueba JUnit desde ExtensionContext .


 Environment environment = SpringExtension.getApplicationContext(context).getEnvironment(); 

Puede echar un vistazo al código completo de la clase TestEnabledCondition .


Crear pruebas


Creemos algunas pruebas e intentemos controlar su lanzamiento:


 @SpringBootTest public class SkiptestApplicationTests { @TestEnabled(property = "app.skip.test.first") @Test public void testFirst() { assertTrue(true); } @TestEnabled(property = "app.skip.test.second") @Test public void testSecond() { assertTrue(false); } } 

Nuestro archivo application.properties ve así:


 app.skip.test.first=true app.skip.test.second=false 

Entonces ...


Resultado de inicio:



El siguiente paso es separar los prefijos de nuestras propiedades en la anotación de clase.


Escribir los nombres completos de las propiedades de application.properties antes de cada prueba es una tarea tediosa. Por lo tanto, es razonable colocar su prefijo en el nivel de la clase de prueba, en una anotación separada.


Crear anotaciones para almacenar prefijos - TestEnabledPrefix :


 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestEnabledPrefix { String prefix(); } 

Procesando y usando la anotación TestEnabledPrefix


Procedemos al procesamiento de la nueva anotación.


Creemos una clase auxiliar AnnotationDescription


Con esta clase, podemos almacenar el nombre de la propiedad de application.properties y su valor.


 public class TestEnabledCondition implements ExecutionCondition { static class AnnotationDescription { String name; Boolean annotationEnabled; AnnotationDescription(String prefix, String property) { this.name = prefix + property; } String getName() { return name; } AnnotationDescription setAnnotationEnabled(Boolean value) { this.annotationEnabled = value; return this; } Boolean isAnnotationEnabled() { return annotationEnabled; } } /* ... */ } 

Esta clase es útil para nosotros, porque vamos a usar expresiones lambda.


Creemos un método que extraiga el valor de la propiedad del prefijo de la anotación de la clase TestEnabledPrefix


 public class TestEnabledCondition implements ExecutionCondition { /* ... */ private AnnotationDescription makeDescription(ExtensionContext context, String property) { String prefix = context.getTestClass() .map(cl -> cl.getAnnotation(TestEnabledPrefix.class)) .map(TestEnabledPrefix::prefix) .map(pref -> !pref.isEmpty() && !pref.endsWith(".") ? pref + "." : "") .orElse(""); return new AnnotationDescription(prefix, property); } /* ... */ } 

Y ahora verifiquemos el valor de la propiedad de application.properties por el nombre especificado en la anotación de prueba


 public class TestEnabledCondition implements ExecutionCondition { /* ... */ @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { Environment environment = SpringExtension.getApplicationContext(context).getEnvironment(); return context.getElement() .map(e -> e.getAnnotation(TestEnabled.class)) .map(TestEnabled::property) .map(property -> makeDescription(context, property)) .map(description -> description.setAnnotationEnabled(environment.getProperty(description.getName(), Boolean.class))) .map(description -> { if (description.isAnnotationEnabled()) { return ConditionEvaluationResult.enabled("Enabled by property: "+description.getName()); } else { return ConditionEvaluationResult.disabled("Disabled by property: "+description.getName()); } }).orElse( ConditionEvaluationResult.enabled("Enabled by default") ); } } 


El código de clase completo está disponible aquí.


Usando la nueva anotación


Ahora aplique nuestra anotación a la clase de prueba :


 @SpringBootTest @TestEnabledPrefix(property = "app.skip.test") public class SkiptestApplicationTests { @TestEnabled(property = "first") @Test public void testFirst() { assertTrue(true); } @TestEnabled(property = "second") @Test public void testSecond() { assertTrue(false); } } 

Ahora nuestro código de prueba es más limpio y simple.


Quiero agradecer a los usuarios de reddit por sus consejos:


1) dpash por consejo
2) BoyRobot777 para consejos


PS


El artículo es una traducción. La versión en inglés se publica en el archivo README.md junto al código del proyecto.

Source: https://habr.com/ru/post/464881/


All Articles