第2章 测试环境搭建:Gradle配置、依赖引入、测试目录结构
搭建一套干净、可复用的测试环境,是系统回归用例落地的基础。我记得刚接手团队时,不少同事的测试代码跑不起来——不是缺依赖就是目录结构混乱,排查一圈下来发现是Gradle配置没对齐。说白了,这一步要是没弄好,后面写再多用例都没法执行。
这一章我带大家从零搭一套标准的AndroidX Test环境,包括Gradle配置、依赖引入和测试目录结构。放心,我会把踩过的坑一块儿说清楚。
2.1 Gradle 配置——让项目认识你的测试
测试环境搭建的第一步,是告诉Gradle“我要跑测试”。我个人习惯每一步都从新建空项目开始,然后逐项添加配置,这样万一一跑就红了,能立刻定位到哪条配置没写对。
先看项目级 build.gradle(或 build.gradle.kts)。注意 —— 现在主流基本都切 Kotlin DSL 了,但如果你还在用 Groovy DSL,除了语法差异,概念完全一样。我这里用 Kotlin DSL 演示。
// 项目级 build.gradle.kts
plugins {
id("com.android.application") version "8.2.0" apply false
id("com.android.library") version "8.2.0" apply false
id("org.jetbrains.kotlin.android") version "1.9.20" apply false
}
// 如果你需要所有子模块统一使用某个仓库或插件版本,可以写在这里
allprojects {
repositories {
google()
mavenCentral()
}
}
接下来是模块级 build.gradle.kts。这里要把测试相关的配置塞进去。我曾见过有人把依赖写错位置,导致测试类编译不过——问题就出在 testImplementation 和 androidTestImplementation 搞混了。
// 模块级 build.gradle.kts
android {
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
// 如果你需要针对特定构建类型跑测试,记得加上:
testBuildType = "debug" // 默认就是 debug,但显式写出更安心
}
// 如果你有签名配置、productFlavor,注意测试会默认跑名为“debug”的变体
// 需要跑其他变体?用 testBuildType 指定
}
dependencies {
// 单元测试(JVM 上跑)
testImplementation("junit:junit:4.13.2")
testImplementation("org.mockito:mockito-core:5.10.0")
// 仪表测试(真机/模拟器上跑)
androidTestImplementation("androidx.test:runner:1.5.2")
androidTestImplementation("androidx.test:rules:1.5.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
// 如果你用 Kotlin 扩展,记得加这个:
androidTestImplementation("androidx.test.ext:junit:1.1.5")
// UIAutomator 2.0(可选)
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
}
💡 小技巧:我建议把所有测试依赖版本提取到 gradle.properties 或 buildSrc 中,这样多模块项目升级版本时只需改一处。
例如在 gradle.properties 里写:
espressoVersion=3.5.1
然后在 build.gradle.kts 里用 rootProject.extra["espressoVersion"] 引用。
⚠️ 注意:我曾经遇到过 testBuildType 没显式指定,结果默认用了 debug,但我的 debug 变体关闭了 minify,导致混淆规则没被应用,线上的包测试通过而 debug 包挂了——坑啊。建议线上回归时,单独创建一个 releaseTest 构建类型,并指定 testBuildType = "releaseTest"。
2.2 依赖引入——精准定位,不乱加
依赖引入看似简单,其实就是往 dependencies 块里塞行。但很多人栽在作用域上。你想想看,testImplementation 和 androidTestImplementation 虽然就差一个 android 前缀,但编译范围完全不同。
我整理了一张表,帮你一秒分清:
| 依赖作用域 | 编译范围 | 典型用途 |
|---|---|---|
testImplementation | 仅用于 src/test/(本地单元测试) | JUnit, Mockito, Robolectric |
androidTestImplementation | 仅用于 src/androidTest/(仪器化测试) | Espresso, UIAutomator, AndroidX Test |
implementation | 所有模块及测试均可访问 | 不要滥用,否则测试依赖会泄漏到生产包 |
debugImplementation | 仅 debug 构建类型 | LeakCanary 这样的调试工具 |
说说我个人的一个习惯:把常用的测试依赖版本用 BOM(Bill of Materials)统一管理,而不是各自写死版本号。比如 AndroidX Test 官方提供了 BOM:
// 在 build.gradle.kts 中
dependencies {
// 引入 BOM,统一管理所有 AndroidX Test 版本
androidTestImplementation(platform("androidx.test:test-bom:1.5.0"))
androidTestImplementation("androidx.test:runner")
androidTestImplementation("androidx.test.espresso:espresso-core")
// 注意:有了 BOM 后不用写版本号了,BOM 会帮你锁定
}
好处很明显:升级时只需改 BOM 版本号,所有关联库自动对齐。我之前一个项目就是逐条升级 Espresso、Runner、Rules,结果版本不兼容,测试一堆 NoSuchMethodError。改用 BOM 后,再也没出过这种问题。
2.3 测试目录结构——项目该长什么样
Android Studio 默认生成的测试目录很简单,但我们做系统回归测试,往往需要处理多模块、多环境的场景。先看最基础的结构:
app/
├── src/
│ ├── main/ # 生产代码
│ │ ├── java/
│ │ └── androidTest/ # 注意,这个位置是错误的!坑!
│ ├── test/ # ✅ 正确位置:本地单元测试
│ │ ├── java/ # 测试类放在这里
│ │ └── resources/ # 比如 mock 的 JSON 文件
│ └── androidTest/ # ✅ 正确位置:仪器化测试
│ ├── java/
│ └── assets/ # 测试用资源(如测试APK、图片)
如果你是多模块项目(比如有 :core、:feature-login),每个模块都应有自己的 test/ 和 androidTest/。我曾经踩过一个坑:把公共测试工具类放在 :app 的 androidTest 下,结果其他模块想引用时发现访问不到。解决方案是把公共测试工具提取到 :test-utils 模块,并用 androidTestImplementation(project(":test-utils")) 引入。
对于大型回归测试,我们通常还会额外建一个 sharedTest 目录?不,那不是官方标准。官方做法是用 src/testShared/ 配合构建变体,或者干脆把公共测试代码打包成一个 test-fixtures Gradle 模块。嗯,这里简单提一下,后面专门讲多模块章节时会详细展开。
🔑 核心要点:
- 本地单元测试 →
src/test/java/(JVM) - 仪器化测试 →
src/androidTest/java/(真机/模拟器) - 不要混淆这两个目录,否则测试会跳过
- 每个模块各自维护自己的测试目录
2.4 验证环境——跑一个空测试确认
配置都写好了,怎么知道环境搭对了?跑一个最简单测试就行。
// 在 src/test/java/ 下创建一个空测试类
import org.junit.Test
import org.junit.Assert.*
class SmokeTest {
@Test
fun testEnvironmentIsReady() {
assertEquals(4, 2 + 2)
}
}
执行命令:./gradlew testDebugUnitTest,如果看到 BUILD SUCCESSFUL,说明本地测试环境通了一半。
再在 src/androidTest/java/ 下创建一个类似的仪器化测试:
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
@RunWith(AndroidJUnit4::class)
class InstrumentedSmokeTest {
@Test
fun testAppContextIsNotNull() {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertNotNull(appContext)
}
}
连接设备或模拟器,执行 ./gradlew connectedDebugAndroidTest。跑通的话,恭喜——你的测试环境已经就绪!
🎯 实测小贴士:如果跑仪器化测试时提示 No instrumentation runner found,八成是 testInstrumentationRunner 没配或者配错了。检查 defaultConfig 块里的配置,以及 androidTestImplementation("androidx.test:runner:1.5.2") 是否已引入。
ok,到这里测试环境搭建就算全部搞定了。接下来你就可以放心地往测试目录里塞用例,我会在后面的章节逐步教你怎么组织、怎么写、怎么跑整个系统回归。一步步来,别急。