各子模块必须独立配置 test sourceSet,不可依赖根项目统一设置;需在各自 build.gradle 中显式声明 sourceSets、测试框架依赖、JVM 参数及 systemProperty,并按需配置 androidTest 路径或 flavor 一致性。

不同子模块需要独立的 test sourceSet 配置
Gradle 默认只提供 test 和 androidTest(Android 项目)两个测试源集,但如果你有多个子模块,且每个模块要跑不同框架(比如一个用 JUnit 5 + Mockito,另一个用 TestNG)、不同 JVM 参数、或不同资源路径,就不能靠根项目的统一配置来覆盖——sourceSets.test 是 per-project 的,必须在各子模块自己的 build.gradle 里单独声明。
常见错误是把所有测试配置写在根项目的 subprojects 块里,结果所有模块被强制套用同一套 test { ... },导致某个模块的 @TestInstance(Lifecycle.PER_CLASS) 被另一个模块的 JUnit 4 环境报错,或者资源路径冲突。
- 每个子模块的
build.gradle中显式配置sourceSets,不要依赖根项目统一 apply - 如果要用不同测试引擎,确保对应模块的
dependencies中只引入目标框架(例如只加testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0',不混junit:junit:4.13.2) - 避免在
allprojects或subprojects里写test { useJUnitPlatform() }这类全局开关;改用afterEvaluate按模块名条件判断
Android 模块中 androidTest 需要单独控制 flavor 维度
Android 项目里,androidTest 默认继承主模块的 flavorDimensions 和 productFlavors。但如果你的 :feature-login 模块只支持 internal flavor,而 :core-network 模块要同时测 internal 和 production,直接共用一套 androidTest 就会失败:Gradle 找不到 src/androidTestProduction/java/ 目录,或报 Cannot create variant 'androidTestProduction' because the dimension 'mode' is not present。
解决方法不是删 flavor,而是让测试源集“按需加载”:
- 在对应模块的
android { ... }块内,用androidTestSourceSet显式指定路径,例如:androidTest.setRoot('src/test-android')(绕过 flavor 自动映射) - 如果仍需 flavor 区分,确保所有参与构建的模块定义完全一致的
flavorDimensions和productFlavors,哪怕某个模块只是空实现 - 禁用不需要的变体:在模块级
android { publishing { singleVariant('debug') { withSourcesJar() } } }不影响测试,但可减少构建变体爆炸
test task 参数不能跨模块继承,必须重写
你可能想在根项目里统一设置 test.jvmArgs = ['-Xmx2g'],但实际执行时,子模块的 test task 并不会自动继承这个值——Gradle 的 task 配置是 lazy-evaluated 且隔离的,除非你显式调用 configureEach 或在子项目中重新赋值。
更麻烦的是,某些参数(如 systemProperty)如果在父项目设了,子模块没覆盖,就会被带入,造成意外交互。比如根项目设了 systemProperty 'spring.profiles.active', 'test',而某个纯数据模块根本不用 Spring,结果启动时抛 NoClassDefFoundError。
- 用
tasks.withType(Test)在每个子模块的build.gradle中单独配置,而不是在根项目subprojects里统一配 - 需要差异化 JVM 参数时,用
test { jvmArgs = project.findProperty('testJvmArgs') ?: ['-Xmx1g'] },再通过gradle.properties或命令行传参控制 - 避免在
test { systemProperties = [...] }中硬编码环境标识,改用project.hasProperty('env')动态判断
多模块共享测试工具类但不共享测试配置
你可能会建一个 :test-utils 模块放通用断言、Mock 工具、TestContainers 配置等,然后让其他模块 testImplementation project(':test-utils')。这本身没问题,但容易忽略一点:这些工具类的运行环境(JDK 版本、ClassLoader 行为、静态初始化顺序)受调用方模块的 test task 配置约束,而不是 :test-utils 自己的配置。
也就是说,:test-utils 里写的 static { System.setProperty("io.netty.leakDetection.level", "paranoid") },最终是否生效,取决于引用它的模块有没有开启 fork = true、有没有设置 classpath 顺序。
-
:test-utils模块自身不需要testtask,可以移除apply plugin: 'java-library'后的test相关配置 - 若工具类依赖特定测试框架(如 AssertJ),只在
api里声明,不拉 runtime 依赖到使用者的 classpath - 在消费者模块中,用
test { classpath = files(...) + classpath }显式调整顺序,确保:test-utils的 jar 在最前
真正难处理的不是怎么配,而是哪个模块该配什么、什么时候会被覆盖、以及 IDE(尤其是 Android Studio)同步后会不会把自定义 sourceSet 当成无效路径标红——这些都得靠每个模块独立声明来保底。

















