如何让ArchUnit测试可复用?多Maven模块适配方案问询
复用ArchUnit测试确保各Maven模块生产类对应测试类的解决方案
核心思路
要解决单模块测试无法复用的问题,关键是把测试逻辑从硬编码的@Test方法中抽离,封装成可共享的ArchRule,同时让规则能适配不同Maven模块的包结构,动态加载当前模块的生产类和测试类。
解决方案实现
1. 封装通用的ArchRule工具类
创建一个可被所有模块依赖的测试工具类(建议放在独立的测试工具模块),把核心检查逻辑封装成通用方法:
import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.ConditionEvents; import com.tngtech.archunit.lang.SimpleConditionEvent; import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; import java.util.Set; import java.util.stream.Collectors; public class ClassTestCoverageRules { // 通用方法:根据根包生成检查规则 public static ArchRule eachProductionClassHasCorrespondingTestClass(String rootPackage) { // 加载当前模块的生产类(排除测试类、JAR包内容) JavaClasses productionClasses = new ClassFileImporter() .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) .importPackages(rootPackage) .that(clazz -> !clazz.isEnum()) .that(clazz -> clazz.isInterface() && clazz.getMethods().stream().anyMatch(JavaMethod::isDefault)); // 匹配原代码的接口筛选逻辑 // 加载当前模块的测试类名称集合 Set<String> testClassNames = new ClassFileImporter() .withImportOption(ImportOption.Predefined.ONLY_INCLUDE_TESTS) .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) .importPackages(rootPackage) .stream() .map(JavaClass::getSimpleName) .collect(Collectors.toSet()); // 定义检查条件:生产类必须有对应Test结尾的测试类 ArchCondition<JavaClass> hasMatchingTestClass = new ArchCondition<>("have a corresponding test class") { @Override public void check(JavaClass clazz, ConditionEvents events) { String expectedTestName = clazz.getSimpleName() + "Test"; if (!testClassNames.contains(expectedTestName)) { events.add(SimpleConditionEvent.violated(clazz, String.format("未找到对应测试类: %s", clazz.getFullName()))); } } }; return ArchRuleDefinition.classes().should(hasMatchingTestClass).check(productionClasses); } // 简化调用:通过锚点类自动推导根包(无需硬编码包名) public static ArchRule eachProductionClassHasCorrespondingTestClass(Class<?> anchorClass) { String rootPackage = getParentPackage(anchorClass); return eachProductionClassHasCorrespondingTestClass(rootPackage); } // 工具方法:获取锚点类的父包(和原代码包名处理逻辑一致) private static String getParentPackage(Class<?> clazz) { String fullPackage = clazz.getPackageName(); return fullPackage.substring(0, fullPackage.lastIndexOf('.')); } }
2. 在各Maven模块中快速复用
每个模块只需创建一个简单的测试类,用@ArchTest引用通用规则即可:
import com.tngtech.archunit.lang.ArchTest; public class ModuleTestCoverageCheck { // 方式1:自动推导当前模块根包(推荐,无需硬编码) @ArchTest public static final ArchRule PRODUCTION_CLASSES_REQUIRE_TESTS = ClassTestCoverageRules.eachProductionClassHasCorrespondingTestClass(ModuleTestCoverageCheck.class); // 方式2:手动指定根包(适用于包结构特殊的模块) // @ArchTest // public static final ArchRule PRODUCTION_CLASSES_REQUIRE_TESTS = // ClassTestCoverageRules.eachProductionClassHasCorrespondingTestClass("com.yourcompany.yourmodule"); }
3. 关键优化说明
- 包名处理简化:用字符串截取替代原代码的数组拆分逻辑,更简洁高效
- 内置逻辑替代自定义类:用Lambda表达式替代
IsNotAnEnum、IsInterfaceWithDefaultMethods等自定义条件类,减少冗余代码 - 类路径隔离:通过
DO_NOT_INCLUDE_JARS确保只加载当前模块的类,避免混入依赖包内容 - 规则复用性:核心逻辑抽离后,任何模块只需依赖测试工具类即可启用检查,无需重复编写逻辑
注意事项
- 确保测试工具模块被其他业务模块的
testscope依赖,Maven依赖配置示例:
<dependency> <groupId>com.yourcompany</groupId> <artifactId>test-utils</artifactId> <version>1.0.0</version> <scope>test</scope> </dependency>
- 若测试类命名规则不是
{类名}Test,可修改expectedTestName的生成逻辑(比如改成{类名}Tests) - 若需要调整生产类的筛选条件(比如排除抽象类),可修改
productionClasses中的that断言逻辑
内容的提问来源于stack exchange,提问作者Oliver
相关产品推荐
相关产品推荐

