本文介绍如何通过 archunit 编写自定义规则,确保所有顶层业务类(非接口、枚举、记录、匿名类)均存在命名规范的对应测试类(如 `service` → `servicetest`),并提供可直接运行的完整实现与关键注意事项。
在构建高可靠性的 Java 项目时,保障测试覆盖率不仅是质量门禁的重要一环,更是持续集成中预防回归缺陷的关键手段。ArchUnit 作为静态架构分析利器,不仅能校验分层、包依赖等宏观结构,还可通过自定义条件精准约束“类-测试类”的映射关系。下面将演示如何强制要求每个待测业务类必须拥有一个命名合规的对应测试类。
ArchUnit 的 ArchCondition 默认以单个 JavaClass 为单位执行检查,但要验证“每个类是否有对应测试类”,需先遍历全部类,提取所有符合 *Test 命名模式的测试类名,并将其映射为被测类名(例如 UserServiceTest → UserService)。这需要重写 init(Collection
@ArchTest
static final ArchRule relevant_classes_should_have_tests =
classes()
.that()
.areTopLevelClasses()
.and().areNotInterfaces()
.and().areNotRecords()
.and().areNotEnums()
.should(haveACorrespondingClassEndingWith("Test"));
private static ArchCondition haveACorrespondingClassEndingWith(String testClassSuffix) {
return new ArchCondition("have a corresponding class with suffix " + testClassSuffix) {
private Set testedClassNames = Collections.emptySet();
@Override
public void init(Collection allClasses) {
this.testedClassNames = allClasses.stream()
.map(JavaClass::getName)
.filter(name -> name.endsWith(testClassSuffix))
.map(name -> name.substring(0, name.length() - testClassSuffix.length()))
.collect(Collectors.toSet());
}
@Override
public void check
(JavaClass clazz, ConditionEvents events) {
// 跳过测试类自身(避免 self-match)
if (clazz.getName().endsWith(testClassSuffix)) {
return;
}
boolean hasCorrespondingTest = testedClassNames.contains(clazz.getName());
String message = String.format(
"%s %s a corresponding test class ending with '%s'",
clazz.getSimpleName(),
hasCorrespondingTest ? "has" : "lacks",
testClassSuffix
);
events.add(new SimpleConditionEvent(clazz, hasCorrespondingTest, message));
}
};
} 通过上述规则,你不仅实现了对测试覆盖率的自动化强约束,更将质量保障左移到编码阶段——每一次 mvn test 或 IDE 运行都会实时反馈缺失的测试类,真正让“有代码必有测试”成为团队可落地的工程实践。