by Emre Savcı
由EmreSavcı
如何使用ArchUnit测试Java项目的体系结构 (How to test your Java project’s architecture with ArchUnit)
In this post, I will show you an interesting library called ArchUnit that I met recently. It does not test your code flow or business logic. The library lets you test your “architecture” including class dependencies, cyclic dependencies, layer accesses, naming conventions, and inheritance checking.
在这篇文章中,我将向您展示我最近遇到的一个有趣的库ArchUnit。 它不会测试您的代码流或业务逻辑。 该库使您可以测试“体系结构”,包括类依赖关系,循环依赖关系,图层访问,命名约定和继承检查。
Here is the list of tests which we will write in this post:
这是我们将在本文中编写的测试列表:
- Cyclic Dependency Test 循环依赖测试
- Layer Access Test 层访问测试
- Class Location Test 课堂位置测试
- Method Return Type Test 方法返回类型测试
- Naming Convention Test 命名约定测试
So let's imagine a project with the package structure shown below:
因此,让我们想象一个具有如下所示的包结构的项目:
Before writing tests for our architecture, as a start point, we decide that our controllers should not be accessed from any other class or package. Also conceptually we accept that controller names should end with the “…Controller” suffix.
首先,为我们的体系结构编写测试之前,我们决定不应从任何其他类或程序包访问控制器。 同样从概念上讲,我们接受控制器名称应以“ ... Controller”后缀结尾。
Now it is time to get our hands dirty. Below, we start writing our first test. It allows us to check our naming convention.
现在是时候弄脏我们的手了。 下面,我们开始编写我们的第一个测试。 它使我们可以检查我们的命名约定。
命名约定测试 (Naming Convention Tests)
@RunWith(ArchUnitRunner.class)@AnalyzeClasses(packages = "com.test.controllers")public class NamingConventionTests { @ArchTest ArchRule controllers_should_be_suffixed = classes() .that().resideInAPackage("..controllers..") .should().haveSimpleNameEndingWith("Controller"); }
When we run the test we see that it passes:
当我们运行测试时,我们看到它通过了:
There are two types of tests with arc unit. One of them is like the one shown above. If we want, we can write tests using JUnit's Test
annotation. Change the RunWith
parameter to JUnit4.class
and remove the AnalyzeClasses
annotation.
电弧单元有两种测试类型。 其中之一就像上面显示的那样。 如果需要,可以使用JUnit的Test
批注编写测试。 将RunWith
参数更改为JUnit4.class
并删除AnalyzeClasses
批注。
In this way, we specify the packages to import using ClassFileImporter
within ArcUnit.
通过这种方式,我们在ArcUnit中指定使用ClassFileImporter
导入的包。
@RunWith(JUnit4.class)public class NamingConventionTests { @Test public void controllers_should_be_suffixed() { JavaClasses importedClasses = new ClassFileImporter().importPackages("com.test.controllers"); ArchRule rule = classes() .that().resideInAPackage("..controllers..") .should().haveSimpleNameEndingWith("Controller"); rule.check(importedClasses); }}
Now, let's see what happens if we have a different suffix. Change ("Controller") to ("Ctrl")
and run:
现在,让我们看看如果使用不同的后缀会发生什么。 将("Controller") to ("Ctrl")
更改("Controller") to ("Ctrl")
并运行:
The exception says that: “java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] — Rule ‘classes that reside in a package ‘..controllers..’ should have a simple name ending with ‘Ctrl’’ was violated (1 time):the simple name of com.test.controllers.FirstController does not end with ‘Ctrl’ in (FirstController.java:0)”
异常表示:“ java.lang。 AssertionError : 违反体系结构 [优先级:中]- 违反了规则'类,位于包'..controllers ..'中,其简单名称以'Ctrl'结尾(1次):com.test的简单名称.controllers。 FirstController在(FirstController.java:0) 中不以'Ctrl'结尾 ”
So far so good. We wrote our first test and it correctly runs. Now it’s time to jump to other tests.
到目前为止,一切都很好。 我们编写了第一个测试,它可以正确运行。 现在该跳到其他测试了。
课堂位置测试 (Class Location Tests)
Let's write another rule that makes sure that classes which have annotation repositories should be located in the infrastructure package.
让我们编写另一条规则,以确保具有批注存储库的类应位于基础结构包中。
@RunWith(ArchUnitRunner.class)@AnalyzeClasses(packages = "com.test")public class RepositoryPackageTest { @ArchTest public ArchRule repositories_should_located_in_infrastructure = classes() .that().areAnnotatedWith(Repository.class) .should().resideInAPackage("..infrastructure..");}
If we annotate other classes than infrastructure packages, the test raises an AssertionError.
如果我们注释除基础结构软件包以外的其他类,则测试将引发AssertionError。
方法返回类型测试 (Method Return Type Tests)
Let's write some method checks. Suppose we decide that our controller methods should return a type BaseResponse.
让我们编写一些方法检查。 假设我们决定控制器方法应返回BaseResponse类型。
@RunWith(ArchUnitRunner.class)@AnalyzeClasses(packages = "com.test.controllers")public class ControllerMethodReturnTypeTest { @ArchTest public ArchRule controller_public_methods_should_return = methods() .that().areDeclaredInClassesThat().resideInAPackage("..controllers..") .and().arePublic() .should().haveRawReturnType(BaseResponse.class) .because("here is the explanation");}
循环依赖测试 (Cyclic Dependency Tests)
In this day and age, cyclic dependency issues are handled by most of the IOC containers. It is a good thing to have some tool that tests it for us.
在当今时代,大多数IOC容器都处理周期性依赖问题。 拥有一些可以为我们测试它的工具是一件好事。
Now first create classes that have cyclic complexity:
现在,首先创建具有循环复杂性的类:
package com.test.services.slice1;import com.test.services.slice2.SecondService;public class FirstService { private SecondService secondService; public FirstService() { this.secondService = new SecondService(); }}
package com.test.services.slice2;import com.test.services.slice1.FirstService;public class SecondService { private FirstService firstService; public SecondService() { this.firstService = new FirstService(); }}
FirstService and SecondService depend on each other, and it creates the cycle.
FirstService和SecondService相互依赖,并创建周期。
Now write a test for it:
现在为它编写一个测试:
@RunWith(ArchUnitRunner.class)@AnalyzeClasses(packages = "com.test")public class CyclicDependencyTest { @ArchTest public static final ArchRule rule = slices().matching("..services.(*)..") .should().beFreeOfCycles();}
Running this test gives the below result:
运行此测试将得出以下结果:
Also, the result is the same as constructor injection.
而且,结果与构造函数注入相同。
层测试 (Layer Tests)
Now it is time to write a layer test which covers our layers.
现在是时候编写覆盖我们各层的层测试了。
@RunWith(JUnit4.class)public class LayeredArchitectureTests { @Test public void layer_dependencies_are_respected() { JavaClasses importedClasses = new ClassFileImporter().importPackages("..com.test.."); ArchRule myRule = layeredArchitecture() .layer("Controllers").definedBy("..com.test.controllers..") .layer("Services").definedBy("..com.test.services..") .layer("Infrastructure").definedBy("..com.test.infrastructure..") .whereLayer("Controllers").mayNotBeAccessedByAnyLayer() .whereLayer("Services").mayOnlyBeAccessedByLayers("Controllers") .whereLayer("Infrastructure").mayOnlyBeAccessedByLayers("Services"); myRule.check(importedClasses); }}
We make a violation of the above rules to see that our test fails — we inject a service into the repository.
我们违反了以上规则,看到我们的测试失败了—我们将服务注入到存储库中。
package com.test.infrastructure;import com.test.services.SecondService;public class FirstRepository { SecondService secondService; public FirstRepository(SecondService secondService) { this.secondService = secondService; }}
When we run the test we will see that our repository violates the rules:
运行测试时,我们将看到我们的存储库违反了规则:
结语 (Wrapping up)
ArchUnit, as you see, ensures that your project has the right architecture. It helps you to keep the project structure clean and prevents developers from making breaking changes.
如您所见,ArchUnit确保您的项目具有正确的体系结构。 它可以帮助您保持项目结构的清洁,并防止开发人员进行重大更改。
We have done a brief overview of the library. Besides all of its features, I think it would be great if ArchUnit would have some rules to test hexagonal architecture, cqrs and some DDD concepts like aggregates, value objects, etc.
我们已经对该库进行了简要概述。 除了所有功能之外,我认为ArchUnit拥有一些规则来测试六角形体系结构 , cqrs和一些DDD概念(如聚合,值对象等)将是很棒的。
For curious ones, here is the code on Github:
对于好奇的人,这里是Github上的代码:
mstrYoda/java-archunit-examplesContribute to mstrYoda/java-archunit-examples development by creating an account on GitHub.github.comTNG/ArchUnitA Java architecture test library, to specify and assert architecture rules in plain Java - TNG/ArchUnitgithub.comUnit test your Java architectureStart enforcing your architecture within 30 minutes using the test setup you already have.www.archunit.org
mstrYoda / java-archunit-examples 通过在GitHub上创建一个帐户为mstrYoda / java-archunit-examples开发做出贡献。 github.com TNG / ArchUnit 一个Java架构测试库,用于以纯Java指定和声明架构规则 -TNG / ArchUnit github.com 对Java架构进行单元测试 开始使用已有的测试设置在30分钟内开始实施架构。 www.archunit.org
翻译自: https://www.freecodecamp.org/news/java-archunit-testing-the-architecture-a09f089585be/