第2章 测试环境搭建:Gradle配置、依赖引入、测试目录结构

搭建一套干净、可复用的测试环境,是系统回归用例落地的基础。我记得刚接手团队时,不少同事的测试代码跑不起来——不是缺依赖就是目录结构混乱,排查一圈下来发现是Gradle配置没对齐。说白了,这一步要是没弄好,后面写再多用例都没法执行。

这一章我带大家从零搭一套标准的AndroidX Test环境,包括Gradle配置、依赖引入和测试目录结构。放心,我会把踩过的坑一块儿说清楚。

AndroidX Test 测试环境搭建流程 Gradle配置 build.gradle / kotlin DSL 依赖引入 AndroidX Test / JUnit / Mockito 测试目录结构 androidTest / test / sharedTest 启用测试支持 testInstrumentationRunner 单元测试依赖 JUnit4 / Mockito-core 仪表测试依赖 Espresso / UIAutomator 项目级 allprojects ⚠ 常见坑 testBuildType 需显式指定 Kotlin版本与AGP兼容 ✅ 最佳实践 统一用 gradle.properties 依赖版本用 BOM 管理 🛠 工具推荐 gradle-wrapper 保持最新 使用 buildSrc 或 version catalog 三个环节层层递进,缺一不可 配置 → 依赖 → 目录,打好地基再写用例

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,到这里测试环境搭建就算全部搞定了。接下来你就可以放心地往测试目录里塞用例,我会在后面的章节逐步教你怎么组织、怎么写、怎么跑整个系统回归。一步步来,别急。