Customizando a composição dos testes JUnit5 com application.properties

Imagine uma situação em que seu projeto precise ser compilado em vários ambientes.


Agora imagine que nem todos os testes devem passar nesses ambientes - cada um tem seu próprio conjunto de testes.


E é preferível configurar a escolha de quais testes devem ser executados no arquivo ... application.properties - cada teste tem seu próprio botão liga / desliga.


Parece ótimo, não é?


Bem-vindo ao gato, onde implementamos tudo isso usando o SpringBoot 2 e o JUnit 5.


Predefinições


Primeiro, vamos desativar o JUnit 4, que vem por padrão no SpringBoot 2, e ativar o JUnit 5 .


Para fazer isso, faça alterações no 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> 

Solução proposta


Queremos anotar cada teste com uma anotação simples com uma propriedade indicando se o teste está ativado ou não. Deixe-me lembrá-lo de que vamos armazenar os valores dessa propriedade no arquivo application.properties .


Anotação


Crie uma anotação :


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

Processamento de anotação


Um manipulador de anotação é indispensável.


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

Você deve criar uma classe (sem a anotação do Spring @Component ) que implemente a interface ExecutionCondition .


Nesta classe, você precisa implementar um método dessa interface - ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) .


Este método pega o contexto do teste executado por JUnit e retorna a condição, independentemente de o teste ser executado ou não.


Leia mais sobre a execução condicional dos testes do JUnit5 na documentação oficial.


Mas como verificamos o valor da propriedade registrada em application.properties nesse caso?


Obtendo acesso ao contexto Spring a partir do contexto JUnit


Dessa forma, podemos obter o ambiente Spring com o qual nosso teste JUnit foi executado no ExtensionContext .


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

Você pode dar uma olhada no código completo da classe TestEnabledCondition .


Criar testes


Vamos criar alguns testes e tentar controlar o lançamento deles:


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

Nosso arquivo application.properties fica assim:


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

Então ...


Resultado da Inicialização:



A próxima etapa é separar os prefixos de nossas propriedades na anotação de classe


Escrever os nomes completos das propriedades em application.properties antes de cada teste é uma tarefa tediosa. Portanto, é razoável colocar seu prefixo no nível da classe de teste - em uma anotação separada.


Crie anotação para armazenar prefixos - TestEnabledPrefix :


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

Processando e usando a anotação TestEnabledPrefix


Prosseguimos com o processamento da nova anotação.


Vamos criar uma classe auxiliar AnnotationDescription


Com essa classe, podemos armazenar o nome da propriedade em application.properties e seu 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; } } /* ... */ } 

Essa classe é útil para nós, porque nós vamos usar expressões lambda.


Vamos criar um método que extrairá o valor da propriedade prefix da anotação da classe 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); } /* ... */ } 

E agora vamos verificar o valor da propriedade em application.properties pelo nome especificado na anotação de teste


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


O código completo da turma está disponível aqui.


Usando a nova anotação


Agora aplique nossa anotação à classe de teste :


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

Agora, nosso código de teste é mais limpo e simples.


Quero agradecer aos usuários do reddit por seus conselhos:


1) dpash para aconselhamento
2) BoyRobot777 para obter conselhos


PS


O artigo é uma tradução. A versão em inglês é publicada no arquivo README.md ao lado do código do projeto.

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


All Articles