贝利信息

ArchUnit 实战:强制要求每个业务类都具备对应测试类

日期:2025-12-29 00:00 / 作者:聖光之護

本文介绍如何使用 archunit 编写自定义规则,确保项目中所有顶层业务类(非接口、枚举、记录、匿名类)均存在命名规范的对应测试类(如 `userservice` → `userservicetest`),并通过 `archcondition.init()` 实现跨类依赖检查。

在架构治理实践中,保障测试覆盖率不仅是质量目标,更是可维护性的基石。ArchUnit 本身不直接提供“类必须有对应测试”的内置断言,但其高度可扩展的 ArchCondition 机制允许我们构建精准、可复用的约束规则。核心思路是:先扫描全部类,提取所有以 Test 结尾的测试类名(并剥离后缀),再逐一校验每个待检业务类是否能在该集合中找到匹配项

以下是一个生产就绪的完整实现:

@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 = Set.of(); // 初始化为空不可变集 @Override public void init(Collection allClasses) { // 一次性预处理:收集所有测试类对应的被测类全限定名(如 UserServiceTest → com.example.UserService) testedClassNames = allClasses.stream() .map(JavaClass::getName) .filter(name -> name.endsWith(testClassSuffix)) .map(name -> name.substring(0, name.length() - testClassSuffix.length())) .collect(Collectors.toUnmodifiableSet()); } @Override public void check(JavaClass clazz, ConditionEvents events) { // 跳过测试类自身(避免自检),只检查业务类 if (clazz.getName().endsWith(testClassSuffix)) { return; } boolean hasCorrespondingTest = testedClassNames.contains(clazz.getName()); String message = String.format( "%s %s a corresponding %s class", clazz.getSimpleName(), hasCorrespondingTest ? "has" : "lacks", testClassSuffix ); events.add(new SimpleConditionEvent(clazz, hasCorrespondingTest, message)); } }; }

关键设计说明:

⚠️ 注意事项:

通过该规则,团队可在 CI 流程中自动拦截未覆盖的类,将“测试先行”从开发习惯固化为架构契约。