Durante o desenvolvimento de um software, costumamos definir algumas convenções que os desenvolvedores devem seguir para que o projeto fique mais organizado, como por exemplo, classes de entidade devem ficar em um pacote específico, toda classe que contém determinada anotação deve ter um sufixo X, etc.
Porém mesmo definindo um padrão, é muito provável que em algum momento esqueçamos de algo, principalmente quando falamos de integrantes novos na equipe. Para resolver esse problema e ter uma garantia de qualidade a mais, podemos criar testes arquiteturais. Os testes de arquitetura além de trazer uma garantia de qualidade também são de baixíssimo custo, pois não dependem de constantes implementações.
Neste artigo irei explicar como escrever estes testes utilizando uma biblioteca chamada ArchUnit.
Apresentação do projeto
Para iniciarmos, vamos primeiro conhecer a estrutura base do nosso projeto.
Criou-se um simples programa usando Spring Boot contendo apenas uma
entidade Pessoa
e um controlador para comunicação externa. A estrutura se parece com isso:

Em nosso pom.xml
, adicionamos a dependência do ArchUnit.
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>0.18.0</version>
<scope>test</scope>
</dependency>
Neste artigo iremos criar dois tipos de testes:
- Um para validar se os controladores e entidades estão nos seus devidos pacotes.
- Outro para verificar se os controladores tem o sufixo
Controller
e as entidades o sufixoEntity
.
Sendo assim, iremos criar duas classes de testes, ambas ficarão dentro de um pacote chamado arch
.

Criação dos testes
Vamos começar pelos testes de nomenclatura. Primeiramente em nossa classe NomenclaturaTest
devemos dizer ao ArchUnit quais classes devem ser analisadas usando a anotação AnalyzeClasses
e
informando o pacote principal:
import com.tngtech.archunit.junit.AnalyzeClasses;
@AnalyzeClasses(packages="br.com.felixgilioli.testesarquiteturais")
public class NomenclaturaTest {
}
Também adicionaremos um import estático que ajudará a escrever os testes, retornando todas as classes que serão encontradas.
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
Diferente do JUnit onde criamos um método para cada teste,
aqui iremos criar uma constante do tipo ArchRule
que conterá a anotação ArchTest
.
Agora devemos buscar todas as classes que contém a anotação RestController
e
verificarmos se ela tem o sufixo Controller, fazemos isso da seguinte forma.
@ArchTest
public static final ArchRule classesAnotadasComRestControllerDevemTerminarComOSufixoController = classes()
.that().areAnnotatedWith(RestController.class)
.should().haveSimpleNameEndingWith("Controller")
.andShould().haveSimpleNameNotEndingWith("RestController")
.as("Classes anotadas com @RestController devem ter o sufixo 'Controller'.");
O método classes()
como foi explicado acima, retorna todas as classes que foram encontradas,
já o that()
nos permite filtrar apenas as classes que respeitam determinada condição,
no nosso caso, todas que são anotadas com RestController. Por sua vez,
o método should()
faz a assertiva para validar se a classe termina com o sufixo Controller.
Adicionamos outra assertiva através do andShould()
para verificar se o
nome não termina com RestController ao invés de apenas Controller. Caso o teste falhe,
a mensagem descrita no método as()
será exibida.
Vamos partir agora para o PackageTest. Adicionamos a mesma anotação AnalyzeClasses que foi apresentada acima.
Neste teste, iremos filtrar as mesmas classes que no teste anterior, porém o que mudará é a assertiva, desta vez queremos saber se essa classe se encontra no pacote controller, fazemos isso da seguinte forma.
@ArchTest
public static final ArchRule classesAnotadasComRestControllerDevemFicarNoPacoteController = classes()
.that().areAnnotatedWith(RestController.class)
.should().resideInAPackage("..controller..")
.as("Classes anotadas com @RestController devem ficar no pacote 'controller'.");
Verificamos através do método resideInAPackage()
se a classe esta naquele pacote.
O código final deve ficar desta forma.
NomenclaturaTest.java
package br.com.felixgilioli.testesarquiteturais.arch;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import javax.persistence.Entity;
import org.springframework.web.bind.annotation.RestController;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
@AnalyzeClasses(packages = "br.com.felixgilioli.testesarquiteturais")
public class NomenclaturaTest {
@ArchTest
public static final ArchRule classesAnotadasComEntityDevemTerminarComOSufixoEntity = classes()
.that().areAnnotatedWith(Entity.class)
.should().haveSimpleNameEndingWith("Entity")
.as("Classes anotadas com @Entity devem ter o sufixo 'Entity'.");
@ArchTest
public static final ArchRule classesAnotadasComRestControllerDevemTerminarComOSufixoController = classes()
.that().areAnnotatedWith(RestController.class)
.should().haveSimpleNameEndingWith("Controller")
.andShould().haveSimpleNameNotEndingWith("RestController")
.as("Classes anotadas com @RestController devem ter o sufixo 'Controller'.");
}
PackageTest.java
package br.com.felixgilioli.testesarquiteturais.arch;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import javax.persistence.Entity;
import org.springframework.web.bind.annotation.RestController;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
@AnalyzeClasses(packages = "br.com.felixgilioli.testesarquiteturais")
public class PackageTest {
@ArchTest
public static final ArchRule classesAnotadasComEntityDevemFicarNoPacoteEntity = classes()
.that().areAnnotatedWith(Entity.class)
.should().resideInAPackage("..entity..")
.as("Classes anotadas com @Entity devem ficar no pacote 'entity'.");
@ArchTest
public static final ArchRule classesAnotadasComRestControllerDevemFicarNoPacoteController = classes()
.that().areAnnotatedWith(RestController.class)
.should().resideInAPackage("..controller..")
.as("Classes anotadas com @RestController devem ficar no pacote 'controller'.");
}
Caso alguém tenha interesse em ver o projeto inteiro, pode acessa-lo no meu GitHub. Para mais exemplos e casos de uso podem consultar o site oficial do ArchUnit.
Conclusão
Neste artigo fiz uma breve explicação sobre o que são testes arquiteturais e como implementa-los na prática. Espero ter te ajudado a entender um pouco sobre o que é e como funciona.