写代码十年,我发现一个规律:越资深的工程师,越重视测试。

而新手最常见的误区是:觉得写测试是”额外负担”,是”拖慢进度”的事。

其实恰恰相反——测试是帮你提效的,不是给你添堵的。

为什么要写单元测试

1. 让你敢重构

没有测试的代码,重构就是赌博。你改了 A 处,可能偷偷破坏了 B 处,而你自己完全不知道。

有了测试,重构完跑一遍测试,全绿就说明行为没变。测试是重构的安全网。

2. 让 bug 更早暴露

bug 越晚发现,修复成本越高。单元测试能在开发阶段就抓住大部分逻辑错误,而不是等到线上出问题才返工。

3. 测试本身就是文档

一份好的测试用例,比注释更能说明”这段代码到底该怎么用、边界条件是什么”。

测试金字塔

一个常见的分层模型:

1
2
3
4
5
6
7
      /\
/ \ E2E 测试(少,慢,贵)
/----\
/ \ 集成测试(中)
/--------\
/ \ 单元测试(多,快,便宜)
/------------\

原则:底层多写,顶层少写。

单元测试快、便宜、稳定,应该是主力。E2E 测试慢、贵、脆弱,只覆盖关键链路。

很多团队的误区是”金字塔倒过来”——E2E 写了一大堆,单元测试反而没有,结果测试又慢又容易挂。

怎么写出好的单元测试

1. 遵循 AAA 结构

  • Arrange:准备数据和状态
  • Act:执行被测方法
  • Assert:断言结果
1
2
3
4
5
6
7
8
9
@Test
void calcPrice_shouldApplyDiscount() {
// Arrange
PricingStrategy strategy = new DiscountStrategy(0.8);
// Act
double result = strategy.calc(100);
// Assert
assertEquals(80.0, result);
}

2. 一个测试只测一件事

不要在一个测试方法里塞十几种断言。每个测试聚焦一个行为,失败时才能快速定位。

3. 用 Mock 隔离外部依赖

单元测试要快,就不能真的连数据库、调远程接口。用 Mock 框架(Mockito 等)把这些外部依赖替换成假对象。

4. 关注边界条件

除了正常情况,还要测:空值、零值、最大值、异常输入。很多 bug 恰恰藏在边界里。

什么是 TDD

TDD(Test-Driven Development,测试驱动开发)的节奏是”红-绿-重构”:

  1. 红(Red):先写一个失败的测试(此时还没有实现代码,测试必然失败)
  2. 绿(Green):写最少的代码,让测试通过
  3. 重构(Refactor):在不破坏测试的前提下,优化代码结构

然后循环往复。

TDD 到底值不值得

我对 TDD 的态度比较务实:

它最大的价值,不是”先写测试”这个形式,而是”逼你先想清楚要做什么”。

先写测试,意味着你必须先定义清楚:输入是什么、输出是什么、边界是什么。很多代码写不好,恰恰是因为”没想清楚就开写”。

但我不主张教条地 100% TDD——有些探索性、一次性的代码,先写测试反而浪费。

一个折中的做法:核心业务逻辑用 TDD,探索性代码先写后补测试。

写在最后

测试这件事,最难的其实不是技术,是习惯和心态。

从”写完代码补个测试”到”把测试当成开发的一部分”,是一个认知的转变。

我的经验是:一旦你体验过”重构时有测试兜底”的安心,就再也回不去没有测试的日子了。

一月份的基础系列先到这里。二月份继续,会聊依赖注入、图、排序、二分查找这些硬核内容。