DEV Community

Cover image for Architectural Tests - Standardize your project's architecture now!
Gabriel Augusto Morato dos Santos
Gabriel Augusto Morato dos Santos

Posted on

Architectural Tests - Standardize your project's architecture now!

Introduction

image

Many times when we start a project, the biggest difficulty we have is when organizing our packages, classes and definitions of which class can have access to a certain other class, after defeating this difficulty, our next challenge is to maintain this organization and document for future developers or even us, how we defined the architecture of our project, this is what you will see next!

Why Architectural Testing?

Image description

As mentioned before, in addition to organizing our project more and defining a convention, we managed to have documentation in a very friendly way, as seen below, we saw a rule informing where the classes annotated with Entity should reside:

@ArchTest
    static final ArchRule entities_must_reside_in_a_domain_package =
            classes().that().areAnnotatedWith(Entity.class).should().resideInAPackage("..domain..")
                    .as("Entities must reside in the domain package");
Enter fullscreen mode Exit fullscreen mode

But how about we get our hands-on now? :)

What technologies will we use?

image

For this article in question, we will need to have the following technologies in our project:

  1. Java 8+: Java Programming Language in version 8+
  2. Maven: Dependency management tool in Java.
  3. ArchUnit: Tool that will be used for the development of our architectural tests
  4. Spring: Programming framework

I will not go into too much of all the technologies used because it is not our purpose of this article.

Setting up our project:

image

In order for us to use archunit in our project, we will need to add the following dependency:

<dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit-junit5</artifactId>
    <version>0.18.0</version>
    <scope>test</scope>
</dependency>
Enter fullscreen mode Exit fullscreen mode

After that, we will create a single class called ArchitectureTest in our test package, which will have the following implementation at this first moment:


@AnalyzeClasses(
        packages = "YourPackage",
        importOptions = ImportOption.DoNotIncludeTests.class
)
public class ArchitectureTest {
}
Enter fullscreen mode Exit fullscreen mode

Note: In the example above, an option was inserted so that the tests were not included in our validation, but if you want to define a pattern in the name of your tests too, feel free. :)

Now, let's get to what interests us! =D

Image description

Together, we will define a simple architecture in which we will have Controller classes that will only be able to call Service classes and that will only be able to call Repository classes, as seen in the drawing below:

image

After that, we will define how the name of each class will look and finally, what will be the package that it will reside in, that said, let's create our architecture? :)

Defining the name of our classes:

image

In the 1st step we will define what the name of our classes should be, since we are extremely creative people, we are going to use the name of each annotation at the end of our class, why has no one ever thought of this before? :D

    @ArchTest
    static ArchRule ClassesThatHaveControllerAnnotation_MustEndWithControllerName =
            classes()
                    .that().areAnnotatedWith(Controller.class)
                    .should().haveSimpleNameEndingWith("Controller")
            .as("Dear developer, all our classes that are annotated as Controller, must have the name finalized with Controller");
Enter fullscreen mode Exit fullscreen mode

As seen above, the use of ArchUnit tests are very simple and very intuitive, within our test we are informing that every class that is annotated as Controller, must have the name finalized as *Controller * and in addition we leave a tip for the next developer who doesn't do it the way we planned, using ".as(....)", isn't that cool? :)

I forced an error, changing the name of our class to UserJohn, look how cool what happened:

Image description

After that, we just create the rules for our other classes, but that's for you to try :)

If you can't, don't worry, just open the project's GitHub :)

Defining the residence of our classes:

image

In the 2nd step we will define where each class will reside, the test will be very similar to the previous one, so let's go? :)

@ArchTest
    static ArchRule ClassesThatHaveControllerAnnotation_MustResideInTheControllerPackage =
            classes()
                    .that().areAnnotatedWith(Controller.class)
                    .should().resideInAPackage("..controller..")
                    .as("Dear developer, all our classes that are annotated as Controller, must reside in the *.controller package");
Enter fullscreen mode Exit fullscreen mode

With the example above, we inform in ".resideInAPackage(...)", which will be the place where our class will actually reside.

Defining which class will call which class:

image

Finally, to conclude our challenge, we need to define which class will call the other one, so that in this way, we have control of which class will be able to know the other, for that we will follow the example below:


 @ArchTest
    static final ArchRule ClassesResidingInControllerPackageCannotKnowRepository =
            noClasses().that().resideInAPackage("..controller..")
                    .should().dependOnClassesThat().resideInAPackage("..repository..")
            .as("Repository classes cannot be together with Controller classes :(");

Enter fullscreen mode Exit fullscreen mode

In the example above, we defined that no class that resides in the Controller package can depend on any class that resides in the Repository package, what a sad love story, isn't it? :D

Thanks guys =D

image

I thank all of you who read my article and who accompany me in the creation of my content :)

ArchUnit, has great documentation and helps me a lot in the projects I run in my day to day, what do you think about presenting this in your projects? :)

Top comments (0)