编写第一个单元测试:Android Studio环境搭建,创建并运行简单测试
说实话,很多做Android开发的朋友第一次接触单元测试时,心里会犯嘀咕:这东西到底有没有用?我的经验是——不仅有用,而且越早写越省钱。我在上一个项目中,就因为生产环境漏了一个空指针,结果线上崩了三天才修好。从那以后,我再也不敢轻视单元测试了。
核心观点:单元测试不是“测试人员的活”,而是开发者保质量的最后一道防线。你写的每一条测试,都是在给未来的自己留一条退路。
1. 环境搭建 —— 别嫌麻烦,一步到位
单元测试跑在JVM上,不需要模拟器或真机。所以咱们的环境其实挺轻量。我习惯用Android Studio自带的测试工具链,省心。
- JDK 8+ —— 推荐JDK 11,兼容性好。我遇到过JDK 8在某些Gradle版本下报错,后来统一升到11就再没出过事。
- Android Studio 4.0+ —— 随便哪个版本都行,但最好用最新稳定版。我记得有一次用预览版跑测试,Mockito死活不认 @Mock 注解,折腾了一下午才发现是IDE bug。
- Gradle 7.0+ —— 项目默认的就行。不过要注意,Kotlin项目如果混合Java测试,记得在 build.gradle 里加上 testOptions.unitTests.includeAndroidResources = true。
我的小技巧:新建项目时直接勾选「Include Kotlin support」和「Include tests」。这样 Android Studio 会自动生成 test/ 目录和 JUnit 依赖,省去你手动配置的功夫。
打开 Android Studio,File → New → New Project,选「Empty Activity」,然后下一步下一步。创建成功后,你会看到下面这个目录结构:
app/
├── src/
│ ├── main/java/ ← 生产代码
│ ├── test/java/ ← 单元测试(跑在JVM上)
│ └── androidTest/java/ ← 仪器化测试(需要设备/模拟器)
我们这次只关心 test/ 目录。里面的测试跑得飞快,毫秒级完成。
2. 创建第一个测试类 —— 从写个计算器开始
写个最简单的 Calculator 类,里面就两个方法:加法、除法。你想想看,这种业务逻辑最容易出错,最适合用单元测试来保护。
// src/main/java/com/example/myapp/Calculator.java
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int divide(int a, int b) {
return a / b; // 危险!b=0时会抛异常
}
}
嗯,注意上面的 divide 方法,如果除数为0会抛 ArithmeticException。我们一会儿就用测试把它抓出来。
现在创建测试类。我习惯在 Calculator 类名上右键 → Go To → Test,然后 Android Studio 会弹出窗口,勾选「setUp/@Before」和「tearDown/@After」,测试类名自动生成 CalculatorTest,放在 test/ 目录下。
// src/test/java/com/example/myapp/CalculatorTest.java
import org.junit.Test;
import static org.junit.Assert.*;
public class CalculatorTest {
private Calculator calculator = new Calculator();
@Test
public void add_shouldReturnSum() {
int result = calculator.add(2, 3);
assertEquals("2 + 3 应该等于5", 5, result);
}
@Test(expected = ArithmeticException.class)
public void divide_shouldThrowWhenDivisorIsZero() {
calculator.divide(10, 0);
}
}
这里我用 @Test(expected = ...) 来验证异常。你想想看,如果不写这个测试,可能上线后用户输入个0,App 直接闪退,差评妥妥的。
3. 运行测试 —— 紧张而有趣的一刻
在测试类左侧有个绿色三角形,点击它选择「Run 'CalculatorTest'」。或者用快捷键 Ctrl+Shift+F10(Windows/Linux)或 Cmd+Shift+R(Mac)。
等个两三秒,你会看到测试结果面板:
| 测试方法 | 结果 | 耗时 |
|---|---|---|
| add_shouldReturnSum | ✅ 通过 | 0.002s |
| divide_shouldThrowWhenDivisorIsZero | ✅ 通过 | 0.001s |
恭喜你,第一个单元测试跑通了!
曾经踩过的坑:我刚开始写测试时,总是忘记在 build.gradle 里加 testImplementation 'junit:junit:4.13.2'。结果运行测试时直接报 ClassNotFoundException: org.junit.Test。更坑的是,Android Studio 有时候不会主动提示你缺依赖,你得手动检查。所以第一步先检查一下 app/build.gradle 的 dependencies 块:
dependencies {
testImplementation 'junit:junit:4.13.2'
// 如果要用 Mockito,再加一行
testImplementation 'org.mockito:mockito-core:4.6.1'
}
4. 理解 JUnit 4 常用注解(附一个表格)
JUnit 4 的注解其实不多,我列个表格帮你快速上手:
| 注解 | 作用 | 执行次数 |
|---|---|---|
@Test |
标记一个测试方法 | 每个测试方法执行一次 |
@Before |
每个测试方法运行前执行,用来初始化共用对象 | 每个测试方法前执行一次 |
@After |
每个测试方法运行后执行,用来清理资源 | 每个测试方法后执行一次 |
@BeforeClass |
所有测试方法运行前执行一次,必须 static | 整个测试类只执行一次 |
@AfterClass |
所有测试方法运行后执行一次,必须 static | 整个测试类只执行一次 |
@Ignore |
忽略一个测试方法,不执行 | 不执行 |
我个人习惯用 @Before 放对象初始化,比如 new Calculator()。这样每个测试方法拿到的是全新实例,互不干扰。如果你有耗时的资源(比如数据库连接),可以用 @BeforeClass 只初始化一次。
5. 关于「Android环境」的那些事
你可能听说过「单元测试不需要 Android 环境」。这是对的,但有个例外:如果你的代码调用了 Android 框架的 API(比如 Context、Toast),JVM 上没有这些类,测试就会崩。解决方案有两种:
- 用 Mockito 模拟依赖 —— 我们后面章节会详细讲。
- 把 Android 依赖抽成接口 —— 更清洁的设计。
不过别慌,你刚才写的 Calculator 完全不用 Android 类,所以直接跑没问题。我第一次写测试时就犯过傻,在测试类里引了个 android.util.Log,结果运行时报 RuntimeException: Stub!。后来才知道,那些是桩方法,只能在真机或模拟器上跑。所以记住:test/ 目录下的测试,不要依赖 Android SDK。如果非要用,请用 androidTest/ 目录。
6. 全流程示意图
下面这张图帮你把从环境到运行测试的步骤串起来,方便你对照操作:
7. 写在最后的小总结
说实话,第一次成功跑通测试的感觉,真的挺爽的。你会发现代码变得可以验证了,不再靠猜。而且写完测试后,重构代码的胆子也大了不少——反正测试会帮你兜底。
如果你刚才跟着做了一遍,现在应该已经看到测试通过了。如果没通过,也别急,检查一下这几个地方:
- 测试类是否放在
src/test/java/下? build.gradle里有没有testImplementation 'junit:junit:4.13.2'?- 测试方法有没有加
@Test注解?
大多数新手问题都出在这三个地方。我当初也是这么踩过来的,嗯,多练两次就习惯了。