testFixtures必须显式启用,因Gradle默认不传递test源集;需在提供模块启用java-test-fixtures插件、工具类放src/testFixtures/java,消费者用testImplementation testFixtures(project(':x'))引入。

testFixtures 为什么必须显式启用
Gradle 默认不暴露测试代码给其他模块,哪怕你把工具类写在 src/test/java 下。这是因为测试源集(test)被设计为“仅本模块内使用”,编译产物不参与依赖传递。想让 module-a 调用 common 里的 TestUtils,必须启用 testFixtures 插件并声明依赖类型。
不启用的典型错误现象:
-
Cannot resolve symbol TestUtils—— 在module-a/src/test/java中 import 失败 - 编译通过但运行时报
NoClassDefFoundError—— 因为test类没被打包进test-fixtures.jar
正确做法:
- 在提供工具的模块(如
common)的build.gradle中添加:plugins { id 'java-test-fixtures' } - 确保工具类放在
src/testFixtures/java(不是src/test/java) - 消费者模块(如
module-a)用testImplementation testFixtures(project(':common'))引入
testFixtures 和 implementation 的区别在哪
关键差异不在代码位置,而在生命周期和可见性范围:
-
implementation:只影响编译期 classpath,且不传递给下游模块的测试代码 -
testFixtures:生成独立的test-fixtures.jar,专供其他模块的test源集使用;它不参与主代码构建,也不会污染生产 classpath - 如果你误用
api或implementation暴露测试类,CI 环境可能因打包了测试依赖而失败,或触发安全扫描告警
示例路径对比:
common/ ├── src/ │ ├── main/java/ ← 生产代码,对所有模块的 main 和 test 都不可见 │ ├── test/java/ ← 仅 common 自己的测试能用 │ └── testFixtures/java/ ← 其他模块 test/* 可以 import,但不能出现在 main/* 中
Android 项目里 testFixtures 不生效怎么办
Android Gradle Plugin(AGP)7.0+ 才原生支持 java-test-fixtures,旧版本会静默忽略该插件。若你用的是 AGP 4.2 或 5.x,必须手动配置:
- 在
common/build.gradle中禁用默认 Java 插件,改用com.android.library,再手动定义 source set:android { sourceSets { testFixtures { java.srcDir 'src/testFixtures/java' } } } - 在
module-a/build.gradle中不能写testImplementation testFixtures(...),要改用:dependencies { androidTestImplementation project(path: ':common', configuration: 'testFixtures') }(注意是androidTestImplementation,不是testImplementation) - Android 的单元测试(
test)和设备测试(androidTest)分属不同运行时,务必确认你要共享的是哪一类测试场景
跨模块共享 @BeforeAll / Testcontainers 配置的陷阱
测试工具不只是工具类——像 TestcontainersExtension、SpringBootTest 的复用,容易因 ClassLoader 隔离出问题:
- 如果
common/testFixtures里定义了静态容器启动逻辑,而module-a的测试用了@ExtendWith(TestcontainersExtension.class),但未将common的testFixtures加入运行时 classpath,容器根本不会启动 - JVM 参数冲突:多个模块共用同一套容器配置时,端口绑定、镜像拉取策略需统一,否则
module-b测试可能因module-a占用 5432 端口而失败 - 推荐做法:把容器生命周期管理封装成
Closeable工具类,由每个测试自己try-with-resources启停,避免跨模块隐式状态共享
真正麻烦的从来不是怎么写,而是谁负责清理、谁控制生命周期、以及失败时日志归到哪个模块——这些边界问题,比加一行 testImplementation 更值得花时间对齐。


















