ArchUnit是一个很有用的测试库。ArchUnit不会测试代码流程或业务逻辑,而是用来测试“架构”,包括类依赖关系、循环依赖关系、层访问、命名约定和继承检查等。
下面我们将使用ArchUnit编写几个测试用例:
-
循环依赖测试
-
层访问测试
-
课堂位置测试
-
方法返回类型测试
-
命名约定测试
我们将使用如下所示包结构的Java项目:

进行体系结构编写测试之前,我们约定不应从任何其他类或程序包访问控制器类。同时,从项目角度,我们约定控制器名称应以“ ... Controller”后缀结尾。
下来开始编写第一个测试,用于检查我们的命名约定。
命名约定测试
@RunWith(ArchUnitRunner.class)
@AnalyzeClasses(packages = "com.test.controllers")
public class NamingConventionTests {
@ArchTest ArchRule controllers_should_be_suffixed = classes()
.that().resideInAPackage("..controllers..")
.should().haveSimpleNameEndingWith("Controller");
}
运行测试,可以看到测试通过:

ArchUnit有两种测试类型,其中之一就像上面的测试用例那样。另外,也可以使用JUnit的Test注释编写测试。将RunWith参数更改为JUnit4.class并删除AnalyzeClasses注释。
@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);
}
}
接着来看看如果使用不同的后缀会发生什么。试着改("Controller") 为 ("Ctrl")并运行:

看到测试出了异常:“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)”。
ArchUnit确实帮我们发现了命名规范的问题。
类路径测试
下来加入另一条规则,以确保具有repositories annotation的类应位于infrastructure包里。
@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..");
}
如果我们为基础结构软件包以外的其他类加入repositories annotation,则测试将引发AssertionError。
方法返回类型测试
下来编写方法检查的测试用例。假设我们约定控制器类的方法应返回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");
}
循环依赖测试
首先创建具有循环依赖问题的类:

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和SecondService相互依赖,存在循环依赖问题。
使用ArchUnit来编写一个测试用例:
@RunWith(ArchUnitRunner.class)
@AnalyzeClasses(packages = "com.test")
public class CyclicDependencyTest {
@ArchTest
public static final ArchRule rule = slices().matching("..services.(*)..")
.should().beFreeOfCycles();
}
运行此测试将得出以下结果:

分层结构测试
下来通过ArchUnit来编写覆盖各层的分层结构测试。
@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);
}
}
下面的代码违反了上述规则,代码把service层的类对象注入到存储层了。
package com.test.infrastructure;
import com.test.services.SecondService;
public class FirstRepository {
SecondService secondService;
public FirstRepository(SecondService secondService) {
this.secondService = secondService;
}
}
运行测试时,看到的repository层代码存在违反约定的问题:

本文介绍如何使用ArchUnit测试库进行架构级别的测试,包括命名约定、类路径、方法返回类型、循环依赖及分层结构测试。通过具体示例,展示了如何发现并解决命名规范、依赖关系和层次结构等问题。
1163

被折叠的 条评论
为什么被折叠?



