一、测试基础与Kotlin测试环境搭建

各位同学好,我是你们的讲师。今天咱们聊聊测试这件事。说实话,我见过太多开发者把测试当成「可有可无」的环节,结果上线后被打脸。我自己也吃过这个亏——有一次发布版本,因为一个边界条件没测到,导致用户数据丢失,那叫一个惨。

所以这门课,我会把我在实际项目中踩过的坑、总结的经验,全都掏出来给你。咱们从最基础的东西开始。

1.1 软件测试金字塔

先看一张图,这是我每次带团队都要画的。

UI 测试 (少) 集成测试 (中) 单元测试 (多) 运行慢 · 维护成本高 运行快 · 维护成本低

这个金字塔什么意思呢?说白了就是:底层单元测试要多写,上层UI测试要少写。我见过不少团队,上来就搞UI自动化测试,结果维护成本高得吓人,最后全废弃了。

核心原则:

  • 单元测试:覆盖业务逻辑、工具类、数据层
  • 集成测试:验证模块间交互、数据库、网络
  • UI测试:只覆盖关键用户流程

1.2 单元测试 vs UI测试

这两者有什么区别?我直接给你列个表,一看就明白。

对比维度 单元测试 UI测试
测试对象 单个函数/类 整个界面交互
运行速度 毫秒级 秒级甚至分钟级
依赖环境 纯JVM即可 需要模拟器/真机
稳定性 低(受系统影响)
定位问题 精准到行 只能定位到界面

你想想看,如果每个UI测试都要等模拟器启动、页面加载,那得多浪费时间?所以我个人的习惯是:能用单元测试解决的问题,绝不上UI测试

1.3 Kotlin测试框架概览

Kotlin的测试生态其实很成熟。我常用的就这三个:

JUnit 5

这是Java/Kotlin测试的基石。说白了,它就是一套「测试规范」——告诉你怎么写测试、怎么运行测试。Kotlin对JUnit的支持非常好,可以直接用lambda表达式写断言。

// 一个简单的JUnit测试
@Test
fun `加法应该返回正确结果`() {
    val result = 1 + 1
    assertEquals(2, result)
}

Mockito

Mockito用来做「模拟」。什么意思呢?比如你的ViewModel依赖一个网络接口,但你不想真的去请求网络,那就用Mockito模拟这个接口的返回值。

我的经验:Mockito配合Kotlin的mockito-kotlin扩展库,写起来特别顺手。我曾经用Mockito模拟了一个复杂的数据库操作,省去了搭建测试环境的麻烦。

// 使用Mockito模拟依赖
val mockRepo = mock<UserRepository>()
`when`(mockRepo.getUserName()).thenReturn("张三")

val viewModel = UserViewModel(mockRepo)
assertEquals("张三", viewModel.userName)

Espresso

Espresso是Google官方的UI测试框架。它可以直接操作界面上的控件,模拟点击、输入等操作。不过要注意,Espresso测试只能在Android设备或模拟器上运行。

// Espresso UI测试示例
@Test
fun `点击登录按钮应该跳转主页`() {
    onView(withId(R.id.btn_login)).perform(click())
    onView(withId(R.id.tv_welcome)).check(matches(isDisplayed()))
}

1.4 在Android Studio中配置测试环境

这部分我手把手带你走一遍。其实配置很简单,但有几个坑要注意。

第一步:检查依赖

打开你的build.gradle.kts(模块级别),确保有以下依赖:

dependencies {
    // 单元测试
    testImplementation("junit:junit:4.13.2")
    testImplementation("org.mockito:mockito-core:5.3.1")
    testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0")
    
    // UI测试
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    androidTestImplementation("androidx.test:runner:1.5.2")
}

注意:我曾经遇到过一个问题——mockito-kotlin和mockito-core版本不兼容,导致运行时崩溃。建议统一使用最新稳定版。

第二步:创建测试目录

Android Studio默认会生成两个测试目录:

  • src/test/java/ — 放单元测试(运行在JVM上)
  • src/androidTest/java/ — 放UI测试(运行在设备上)

别搞混了。我见过有人把UI测试写在test目录下,结果跑不起来。

第三步:配置测试运行器

defaultConfig里加上:

android {
    defaultConfig {
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
}

1.5 创建第一个测试用例

好了,理论说完了,咱们动手写一个。我选一个最简单的场景——测试一个计算器工具类。

被测试的代码

// Calculator.kt
class Calculator {
    fun add(a: Int, b: Int): Int = a + b
    
    fun divide(a: Int, b: Int): Int {
        if (b == 0) throw IllegalArgumentException("除数不能为0")
        return a / b
    }
}

编写单元测试

// CalculatorTest.kt
import org.junit.Test
import org.junit.Assert.*

class CalculatorTest {
    
    private val calculator = Calculator()
    
    @Test
    fun `加法_正数相加_返回正确结果`() {
        val result = calculator.add(3, 5)
        assertEquals(8, result)
    }
    
    @Test
    fun `加法_负数相加_返回正确结果`() {
        val result = calculator.add(-1, -2)
        assertEquals(-3, result)
    }
    
    @Test(expected = IllegalArgumentException::class)
    fun `除法_除数为0_抛出异常`() {
        calculator.divide(10, 0)
    }
    
    @Test
    fun `除法_正常除法_返回正确结果`() {
        val result = calculator.divide(10, 2)
        assertEquals(5, result)
    }
}

小技巧:Kotlin允许在函数名中使用反引号,这样测试方法名可以写成自然语言,可读性特别好。我个人非常推荐这种写法。

运行测试

在Android Studio中,右键点击测试类或方法,选择「Run」即可。你会看到绿色的勾,表示测试通过。

嗯,到这里,你的第一个测试用例就跑起来了。是不是很简单?

小结

这一章我们聊了测试金字塔、单元测试和UI测试的区别,也介绍了JUnit、Mockito、Espresso这三个框架。最后我们亲手写了一个测试用例。记住:测试不是负担,而是保障。我见过太多项目因为缺少测试,改一个功能崩一片。从今天开始,养成写测试的习惯。


公众号:蓝海资料掘金营,微信deep3321