You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Archunit强制要求每个类对应一个测试类?

使用Archunit强制每个类对应测试类的完整实现

你已经完成了目标类的筛选逻辑,接下来只需要在should()后添加自定义条件,验证每个筛选出的类都存在对应的测试类即可。以下是两种常见测试类命名/路径约定下的实现方案:

方案1:测试类以Test为后缀(如UserService → UserServiceTest)

直接通过类名拼接+包内查找的方式验证:

classes().that()
         .areNotInterfaces()
         .and()
         .areNotAnonymousClasses()
         .and()
         .areNotRecords()
         .and()
         .areNotEnums()
         .should(new ArchCondition<JavaClass>("have a matching test class with 'Test' suffix") {
             @Override
             public void check(JavaClass javaClass, ConditionEvents events) {
                 // 拼接测试类名
                 String testClassName = javaClass.getSimpleName() + "Test";
                 // 在同一个包下查找测试类(也可指定固定测试包前缀,比如javaClass.getPackageName().replace("main", "test"))
                 Optional<JavaClass> testClass = javaClass.getPackage().getClassByName(testClassName);
                 
                 if (testClass.isEmpty()) {
                     events.add(SimpleConditionEvent.violated(javaClass, "Missing test class: " + testClassName));
                 }
             }
         });

方案2:测试类与主类包结构完全对应(Maven/Gradle标准结构)

如果你的项目遵循src/main/java和src/test/java的包结构一致约定,可以先分别导入主类和测试类集合,再做匹配检查:

// 导入主代码包下的所有类
JavaClasses mainClasses = new ClassFileImporter()
        .importPackages("com.yourproject");

// 导入测试代码包下的所有类
JavaClasses testClasses = new ClassFileImporter()
        .importPackages("com.yourproject");

// 定义规则
ArchRule testClassRequiredRule = classes().that()
         .areNotInterfaces()
         .and()
         .areNotAnonymousClasses()
         .and()
         .areNotRecords()
         .and()
         .areNotEnums()
         .and()
         .areIn(mainClasses)
         .should(haveCorrespondingTestClass(testClasses));

// 自定义匹配逻辑
private static ArchCondition<JavaClass> haveCorrespondingTestClass(JavaClasses testClasses) {
    return new ArchCondition<>("have a matching test class in the corresponding package") {
        @Override
        public void check(JavaClass javaClass, ConditionEvents events) {
            boolean testClassExists = testClasses.stream()
                    .anyMatch(testClazz -> 
                        // 匹配类名(后缀Test)+ 相同包路径
                        testClazz.getSimpleName().equals(javaClass.getSimpleName() + "Test")
                        && testClazz.getPackageName().equals(javaClass.getPackageName())
                    );
            
            if (!testClassExists) {
                events.add(SimpleConditionEvent.violated(javaClass, 
                    String.format("No test class found for %s in package %s", 
                        javaClass.getSimpleName(), javaClass.getPackageName())));
            }
        }
    };
}

可选优化

  • 排除无需测试的类:比如启动类、配置类,只需在筛选条件中添加.and().areNotAssignableTo(Application.class)或.and().haveSimpleNameNotMatching(".*Config")
  • 适配其他测试类命名:如果测试类是TestXxx前缀,只需修改类名拼接逻辑为"Test" + javaClass.getSimpleName()

内容的提问来源于stack exchange,提问作者kylie.zoltan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 12:30:41