Kotlin 的 internal 是模块级可见性,而 Gradle 的 integrationTest 源集默认为独立编译单元,与 main 不属同一 Kotlin module,故无法访问 internal 成员;需通过显式统一 kotlinOptions.moduleName 实现模块边界合并。

直接说结论:Kotlin 的 internal 是模块级可见性,Gradle 中集成测试(integrationTest)默认属于独立源代码集(source set),不与 main 同属一个模块,因此无法访问 internal 成员。要解决,必须让测试源集“加入”主模块的可见性边界。
为什么 integrationTest 看不到 internal?
Kotlin 的 internal 不是“包内可见”,而是“同一编译单元(module)内可见”。Gradle 的 integrationTest 源代码集默认被配置为独立编译任务,即使它依赖 main 输出,也不会共享 Kotlin 编译器的 module scope。
- 现象:
Cannot access 'xxx': it is internal in 'yyy' - 常见场景:你写了
internal fun createTestUser()在main/kotlin,但integrationTest/kotlin里调用就报错 - 关键点:这不是 classpath 或依赖问题,是 Kotlin 编译器在编译
integrationTest时,根本没把main当作同一个 module
让 integrationTest 和 main 共享 module 边界
最稳妥的方式是显式将 integrationTest 的 Kotlin 编译任务配置为与 main “合并模块”(即共用同一 module name)。需在 build.gradle.kts 中操作:
- 确保你已正确定义了
integrationTest源代码集(例如通过jvmTestSuite或手动sourceSets) - 对
integrationTest的 Kotlin 编译任务,设置moduleName与main一致(默认是项目名,如"mylib") - 示例配置(适用于 Kotlin DSL):
tasks.withType<org.jetbrains.kotlin.gradle.tasks.kotlincompile>().configureEach {
if (name.contains("integrationTest")) {
kotlinOptions.moduleName = "mylib" // 必须和 main 的 moduleName 完全一致
}
}</org.jetbrains.kotlin.gradle.tasks.kotlincompile>
注意:moduleName 默认就是项目 rootProject.name 或 project.name,但显式设一遍更可靠;若你用了 kotlinModule 或自定义 compilerOptions,需统一检查。
替代方案:用 @VisibleForTesting + internal 组合
如果你只是想临时暴露给测试、又不想破坏封装边界,可配合 Kotlin 标准库注解:
- 在
main中声明:@VisibleForTesting(otherwise = VisibleForTesting.INTERNAL) internal fun xxx() - 该注解本身不改变可见性,但它是一种语义提示;更重要的是,它能被 IDE 和静态分析工具识别
- 真正起作用的仍是
internal—— 所以仍需上面的moduleName配置,否则注解无意义 - 不要只加注解而不配
moduleName,否则还是编译失败
容易踩的坑
几个高频误操作:
- 以为加
implementation(project(":"))或testImplementation(sourceSets.main.output)就够了 —— 这只解决 classpath,不解决 Kotlin module 边界 - 在 Groovy 脚本里写
kotlinOptions { moduleName = "xxx" }却没限定 task 范围,导致所有 Kotlin 编译任务都被覆盖 - 混淆
integrationTest和test:默认test源集通常已与main共享 module(尤其在老版 Gradle/KGP 中),但integrationTest几乎总是需要手动干预 - 升级到 AGP 9.0+ 或 KGP 2.2+ 后,
sourceSets.integrationTest.kotlin.srcDirs可能被忽略,必须走tasks.withType配置
最常被忽略的一点:Kotlin 的 module 边界是编译期概念,和 JVM 的 classloader、jar 包、甚至 Gradle 的 source set 名称都无直接关系。你看到的目录结构,对 Kotlin 编译器来说只是输入路径 —— 它只认 moduleName 和实际参与编译的源文件集合。











