单元测试(Unit Testing)

单元测试(Unit Testing)

一句话总结:在代码开发阶段,针对最小可测试单元(函数、方法、类)进行的独立验证测试,确保每个”零件”都能正常工作。


🌟 快速理解(小白入门)

用生活化类比

想象你在组装一台电脑:

  • 🔌 单元测试 = 先测试每个零件(CPU、内存、硬盘)是否正常
  • 🖥️ 集成测试 = 再测试组装后整机是否能开机
  • 🎮 系统测试 = 最后测试能否流畅运行游戏

为什么要先测零件?

如果组装后才发现内存条坏了,你需要:

  1. 拆开整台电脑
  2. 找出是哪个零件坏了
  3. 重新组装

但如果装机前就测试好每个零件,出问题的概率会大大降低!


📌 核心概念

定义

单元测试(Unit Testing) 是一种软件测试方法,针对程序中最小可测试单元(如函数、方法、类)进行正确性验证。

通俗解释

就像搭积木前,先确保每块积木没有瑕疵,这样搭出来的城堡才稳固。

关键特征

特征 说明 为什么重要
隔离性 每个测试独立运行,不依赖其他代码 避免”牵一发动全身”的问题
快速 通常几毫秒内完成 可以频繁运行,快速反馈
🔄 可重复 多次运行结果一致 确保测试结果可靠
📝 自动化 可集成到 CI/CD 流程 节省人力,提高效率
🎯 精准 只测试一个功能点 快速定位问题

🎯 为什么需要单元测试?

真实场景

场景 1:银行转账系统

问题:某银行上线新功能,未做单元测试,结果计算利息的函数有 bug

后果

  • 💸 错误计算了 10 万用户的利息
  • 📰 媒体曝光,声誉受损
  • 💰 赔偿损失 $500 万
  • ⏱️ 修复耗时 1 周

如果做了单元测试

  • ✅ 开发阶段就发现问题
  • ⏱️ 修复只需 1 小时
  • 💰 成本几乎为 0

场景 2:特斯拉自动驾驶

数据:特斯拉通过单元测试,在开发阶段发现 80% 的 bug

效果

  • 🚗 提高行车安全性
  • 📈 减少召回成本
  • ⭐ 提升用户信任度

行业数据

阶段 修复成本 时间成本 数据来源
单元测试阶段 $1 1 小时 Google 研究
集成测试阶段 $10 1 天 IBM 报告
系统测试阶段 $50 3 天 Microsoft 数据
生产环境 $100-$1000 1-4 周 Gartner 统计

结论:越早发现问题,成本越低!


✅ 优势与价值

优势 1:节省时间和金钱 💰

通俗解释

就像体检一样,早发现早治疗,花费少、恢复快。

专业说明

单元测试在开发阶段发现问题,修复成本远低于生产环境。

实际案例

某电商平台的购物车功能:

  • 未做单元测试:上线后发现计算错误,紧急修复耗时 3 天,损失订单 $50 万
  • 做了单元测试:开发阶段发现问题,修复耗时 2 小时,成本几乎为 0

行业最佳实践

  • Google:要求所有新代码必须有单元测试,覆盖率 ≥ 80%
  • Microsoft:每个函数至少 3 个单元测试(正常、边界、异常)
  • Amazon:单元测试失败则无法合并代码

优势 2:提高代码质量 ⭐

通俗解释

单元测试就像给代码做”质检”,确保每个功能都符合标准。

专业说明

通过测试各种场景(正常、边界、异常),确保代码健壮性。

实际案例

某支付系统的金额计算函数:

1
2
3
4
5
# 未测试的代码
def calculate_total(price, quantity):
return price * quantity

# 问题:没考虑负数、小数精度等
1
2
3
4
5
6
7
8
9
10
11
12
13
# 经过单元测试优化的代码
def calculate_total(price, quantity):
if price < 0 or quantity < 0:
raise ValueError("价格和数量不能为负数")

# 使用 Decimal 避免浮点数精度问题
from decimal import Decimal
return Decimal(str(price)) * Decimal(str(quantity))

# 单元测试覆盖了:
# ✅ 正常情况
# ✅ 负数情况
# ✅ 小数精度问题

优势 3:提供活文档 📚

通俗解释

单元测试就像”使用说明书”,告诉别人这个函数怎么用。

专业说明

测试用例展示了函数的预期行为和使用方式,比注释更可靠(因为测试会运行,注释可能过时)。

示例

1
2
3
4
5
6
7
8
9
10
def test_user_login():
"""测试用户登录功能"""
# 正常登录
assert login("user@example.com", "password123") == True

# 错误密码
assert login("user@example.com", "wrong") == False

# 不存在的用户
assert login("nobody@example.com", "password") == False

通过阅读测试,新同事立刻知道 login() 函数的用法!


优势 4:支持重构 🔄

通俗解释

有了单元测试,就像有了”安全网”,可以放心地改代码。

专业说明

重构代码时,单元测试确保功能不被破坏。

实际案例

某团队重构遗留代码:

  • 有单元测试:重构后运行测试,5 分钟内发现 3 个问题,立即修复
  • 无单元测试:重构后上线,1 周后用户反馈功能异常,排查耗时 2 天

⚠️ 挑战与应对

挑战 1:需要编写额外代码 📝

问题表现

测试代码量可能是业务代码的 1-3 倍。

为什么会这样

一个函数通常需要测试多种场景(正常、边界、异常)。

解决方案

  1. 使用测试框架(推荐)

    • Python: pytest, unittest
    • Java: JUnit, TestNG
    • JavaScript: Jest, Mocha
  2. 参数化测试(减少重复代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ❌ 重复代码
def test_add_positive():
assert add(1, 2) == 3

def test_add_negative():
assert add(-1, -2) == -3

# ✅ 参数化测试
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(-1, -2, -3),
(0, 0, 0),
(100, 200, 300),
])
def test_add(a, b, expected):
assert add(a, b) == expected

行业最佳实践

  • Google:测试代码与业务代码比例约 1.5:1
  • 投入产出比:编写测试多花 20% 时间,但减少 80% 的 bug 修复时间

挑战 2:无法覆盖所有场景 🎯

问题表现

不可能测试所有可能的输入组合。

为什么会这样

输入空间太大(如一个接受字符串的函数,可能的输入是无限的)。

解决方案

  1. 等价类划分

    • 将输入分为几类,每类测试一个代表值
  2. 边界值分析

    • 重点测试边界值(如 0、最大值、最小值)
  3. 优先级排序

    • 先测试核心功能和高风险场景

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 测试年龄验证函数
def test_validate_age():
# 等价类:有效年龄
assert validate_age(25) == True

# 边界值:最小有效年龄
assert validate_age(18) == True
assert validate_age(17) == False

# 边界值:最大有效年龄
assert validate_age(120) == True
assert validate_age(121) == False

# 异常情况
assert validate_age(-1) == False
assert validate_age(0) == False

挑战 3:维护成本 🔧

问题表现

代码修改后,需要同步更新测试。

为什么会这样

测试与代码实现紧密耦合。

解决方案

  1. 测试行为而非实现
1
2
3
4
5
6
7
8
9
10
11
# ❌ 测试实现细节(脆弱)
def test_user_service():
service = UserService()
assert service.database.connection is not None
assert service.cache.enabled == True

# ✅ 测试行为(稳定)
def test_user_service():
service = UserService()
user = service.get_user(123)
assert user.name == "张三"
  1. 使用 Mock 隔离依赖
1
2
3
4
5
6
7
8
9
# 使用 Mock 避免依赖真实数据库
def test_get_user():
mock_db = Mock()
mock_db.query.return_value = {"id": 123, "name": "张三"}

service = UserService(database=mock_db)
user = service.get_user(123)

assert user.name == "张三"

行业最佳实践

  • 测试金字塔:70% 单元测试 + 20% 集成测试 + 10% E2E 测试
  • 持续集成:每次提交自动运行测试

🔄 单元测试的类型

类型对比

类型 优势 劣势 适用场景
手动单元测试 • 灵活性高
• 可发现意外问题
• 耗时长
• 成本高
• 难以重复
• 探索性测试
• 复杂业务逻辑
自动化单元测试 • 快速
• 可重复
• 成本低
• 初期投入大
• 无法发现所有问题
• 回归测试
• CI/CD 流程
• 大规模项目

1. 手动单元测试

定义:由测试人员手动编写和执行测试代码。

适用场景

  • 🔍 探索新功能
  • 🧪 验证复杂业务逻辑
  • 🎯 一次性测试

示例

测试人员手动运行代码,观察输出:

1
2
3
4
5
6
7
8
9
10
11
# 手动测试
def test_calculate_discount():
# 测试人员手动运行并检查结果
result = calculate_discount(price=100, discount=0.2)
print(f"结果: {result}") # 期望: 80

# 手动验证是否正确
if result == 80:
print("✅ 测试通过")
else:
print("❌ 测试失败")

2. 自动化单元测试(推荐)

定义:使用测试框架自动执行测试,无需人工干预。

适用场景

  • 🔄 回归测试(每次修改代码后运行)
  • 🚀 CI/CD 流程(自动化部署)
  • 📈 大规模项目(成百上千个测试)

示例

1
2
3
4
5
6
7
8
9
# 自动化测试(使用 pytest)
def test_calculate_discount():
assert calculate_discount(100, 0.2) == 80
assert calculate_discount(50, 0.1) == 45
assert calculate_discount(200, 0.5) == 100

# 运行命令:pytest test_discount.py
# 输出:
# ✅ test_calculate_discount PASSED

行业标准

  • Google:99% 的单元测试是自动化的
  • Microsoft:要求所有单元测试可自动运行
  • Amazon:CI/CD 流程中自动运行所有单元测试

🛠️ 好的单元测试的特征

💡 来自 Google 的测试准则

FIRST 原则

特征 英文 说明 示例
F Fast(快速) 几毫秒内完成 ✅ 0.001s
❌ 5s
I Independent(独立) 不依赖其他测试 ✅ 可单独运行
❌ 必须按顺序运行
R Repeatable(可重复) 每次结果一致 ✅ 运行 100 次都通过
❌ 有时通过有时失败
S Self-Validating(自验证) 自动判断通过/失败 ✅ assert 自动判断
❌ 需要人工检查输出
T Timely(及时) 与代码同步编写 ✅ TDD(先写测试)
❌ 代码写完几个月后补测试

详细说明

1. Fast(快速)⚡

为什么重要

测试慢了,开发者就不愿意频繁运行,失去了快速反馈的价值。

标准

  • ✅ 单个测试:< 10ms
  • ✅ 整个测试套件:< 10 分钟

反例

1
2
3
4
5
6
7
8
9
10
11
12
# ❌ 慢测试(依赖真实数据库)
def test_get_user():
db = connect_to_database() # 耗时 2 秒
user = db.query("SELECT * FROM users WHERE id=1")
assert user.name == "张三"

# ✅ 快测试(使用 Mock)
def test_get_user():
mock_db = Mock()
mock_db.query.return_value = {"id": 1, "name": "张三"}
user = get_user(1, database=mock_db)
assert user.name == "张三"

2. Independent(独立)🔒

为什么重要

测试间有依赖,一个失败会导致连锁反应,难以定位问题。

标准

  • ✅ 可以任意顺序运行
  • ✅ 可以并行运行
  • ✅ 可以单独运行

反例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ❌ 有依赖的测试
def test_create_user():
global user_id
user_id = create_user("张三")

def test_update_user():
# 依赖上一个测试的 user_id
update_user(user_id, name="李四")

# ✅ 独立的测试
def test_create_user():
user_id = create_user("张三")
assert user_id is not None

def test_update_user():
# 自己创建测试数据
user_id = create_user("张三")
result = update_user(user_id, name="李四")
assert result == True

3. Repeatable(可重复)🔄

为什么重要

不稳定的测试(Flaky Test)会降低团队对测试的信任。

标准

  • ✅ 运行 1000 次结果一致
  • ✅ 不依赖外部环境(时间、网络、随机数)

反例

1
2
3
4
5
6
7
8
9
10
11
# ❌ 不可重复(依赖当前时间)
def test_is_weekend():
assert is_weekend() == True # 只有周末才通过

# ✅ 可重复(注入时间依赖)
def test_is_weekend():
saturday = datetime(2024, 1, 6) # 2024-01-06 是周六
assert is_weekend(saturday) == True

monday = datetime(2024, 1, 8) # 2024-01-08 是周一
assert is_weekend(monday) == False

4. Self-Validating(自验证)✅

为什么重要

需要人工判断的测试,无法自动化运行。

标准

  • ✅ 使用 assert 自动判断
  • ✅ 明确的通过/失败结果

反例

1
2
3
4
5
6
7
8
9
# ❌ 需要人工判断
def test_calculate():
result = calculate(10, 20)
print(f"结果: {result}") # 需要人工检查是否是 30

# ✅ 自动判断
def test_calculate():
result = calculate(10, 20)
assert result == 30 # 自动判断

🔍 单元测试 vs 集成测试

核心区别

维度 单元测试 集成测试
测试对象 单个函数/类 多个模块的交互
隔离性 完全隔离 真实集成
速度 极快(毫秒级) 较慢(秒级)
依赖 使用 Mock 使用真实依赖
发现问题 逻辑错误 接口不匹配、数据传递错误
运行时机 每次代码修改 每次提交/每天

生活化类比

  • 🔌 单元测试 = 测试每个零件(CPU、内存、硬盘)
  • 🖥️ 集成测试 = 测试组装后的整机
  • 🎮 系统测试 = 测试能否流畅运行游戏

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 单元测试:只测试 calculate_total 函数
def test_calculate_total():
assert calculate_total(100, 2) == 200
assert calculate_total(50, 3) == 150

# 集成测试:测试整个下单流程
def test_place_order():
# 涉及多个模块:购物车、库存、支付
cart = ShoppingCart()
cart.add_item("商品A", 100, 2)

order = place_order(cart)

assert order.total == 200
assert inventory.get_stock("商品A") == 98 # 库存减少
assert payment.is_paid(order.id) == True # 支付成功

🧪 单元测试技术

三种测试技术

技术 别名 关注点 适用场景
黑盒测试 功能测试 输入 → 输出 不关心内部实现
白盒测试 结构测试 代码路径覆盖 关心代码逻辑
灰盒测试 基于错误测试 已知风险点 针对性测试

1. 黑盒测试(功能测试)

定义:只关注输入和输出,不关心内部实现。

示例

1
2
3
4
5
6
7
8
9
10
# 测试登录功能(不关心内部如何验证)
def test_login():
# 正确的用户名和密码
assert login("user@example.com", "password123") == True

# 错误的密码
assert login("user@example.com", "wrong") == False

# 不存在的用户
assert login("nobody@example.com", "password") == False

2. 白盒测试(结构测试)

定义:关注代码内部逻辑,确保所有路径都被测试。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def calculate_discount(price, vip_level):
if price > 1000:
if vip_level == "gold":
return price * 0.7 # 路径 1
else:
return price * 0.8 # 路径 2
else:
if vip_level == "gold":
return price * 0.9 # 路径 3
else:
return price # 路径 4

# 白盒测试:覆盖所有 4 条路径
def test_calculate_discount():
assert calculate_discount(1500, "gold") == 1050 # 路径 1
assert calculate_discount(1500, "silver") == 1200 # 路径 2
assert calculate_discount(500, "gold") == 450 # 路径 3
assert calculate_discount(500, "silver") == 500 # 路径 4

3. 灰盒测试(基于错误测试)

定义:基于已知的风险点和常见错误进行测试。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 已知风险:除零错误、空值、边界值
def test_calculate_average():
# 正常情况
assert calculate_average([1, 2, 3]) == 2

# 风险 1:空列表(除零)
with pytest.raises(ValueError):
calculate_average([])

# 风险 2:None 值
with pytest.raises(TypeError):
calculate_average(None)

# 风险 3:边界值
assert calculate_average([0]) == 0
assert calculate_average([1000000]) == 1000000

🛠️ 主流测试框架与工具

按语言分类

语言 推荐框架 特点 学习曲线 社区活跃度
Python pytest 简洁、强大、插件丰富 ⭐⭐ ⭐⭐⭐⭐⭐
Python unittest 标准库、类似 JUnit ⭐⭐⭐ ⭐⭐⭐⭐
JavaScript Jest 零配置、快照测试 ⭐⭐ ⭐⭐⭐⭐⭐
JavaScript Mocha 灵活、可定制 ⭐⭐⭐ ⭐⭐⭐⭐
Java JUnit 5 行业标准、注解丰富 ⭐⭐⭐ ⭐⭐⭐⭐⭐
Java TestNG 更强大、支持并行 ⭐⭐⭐⭐ ⭐⭐⭐⭐
C# NUnit 类似 JUnit ⭐⭐⭐ ⭐⭐⭐⭐
C# xUnit 现代化、推荐 ⭐⭐ ⭐⭐⭐⭐⭐
Go testing 标准库、简洁 ⭐⭐ ⭐⭐⭐⭐⭐
Ruby RSpec BDD 风格、可读性强 ⭐⭐⭐ ⭐⭐⭐⭐

快速上手示例

Python + pytest

1
2
3
4
5
6
7
8
9
10
11
12
# 安装
pip install pytest

# 编写测试(test_calculator.py)
def test_add():
assert add(1, 2) == 3

def test_subtract():
assert subtract(5, 3) == 2

# 运行测试
pytest test_calculator.py

JavaScript + Jest

1
2
3
4
5
6
7
8
9
10
// 安装
npm install --save-dev jest

// 编写测试(calculator.test.js)
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});

// 运行测试
npm test

💻 实战示例

示例 1:电商购物车

需求:实现购物车的添加商品功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 业务代码
class ShoppingCart:
def __init__(self):
self.items = []

def add_item(self, product_id, name, price, quantity):
if price < 0:
raise ValueError("价格不能为负数")
if quantity <= 0:
raise ValueError("数量必须大于 0")

self.items.append({
"product_id": product_id,
"name": name,
"price": price,
"quantity": quantity
})

def get_total(self):
return sum(item["price"] * item["quantity"] for item in self.items)

# 单元测试
def test_add_item_success():
"""测试正常添加商品"""
cart = ShoppingCart()
cart.add_item(1, "商品A", 100, 2)

assert len(cart.items) == 1
assert cart.get_total() == 200

def test_add_item_negative_price():
"""测试负数价格(应该抛出异常)"""
cart = ShoppingCart()

with pytest.raises(ValueError, match="价格不能为负数"):
cart.add_item(1, "商品A", -100, 2)

def test_add_item_zero_quantity():
"""测试数量为 0(应该抛出异常)"""
cart = ShoppingCart()

with pytest.raises(ValueError, match="数量必须大于 0"):
cart.add_item(1, "商品A", 100, 0)

def test_add_multiple_items():
"""测试添加多个商品"""
cart = ShoppingCart()
cart.add_item(1, "商品A", 100, 2)
cart.add_item(2, "商品B", 50, 3)

assert len(cart.items) == 2
assert cart.get_total() == 350

示例 2:用户登录验证

需求:验证用户登录逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 业务代码
class UserService:
def __init__(self, database):
self.database = database

def login(self, email, password):
user = self.database.find_user_by_email(email)

if user is None:
return False

if user.password != self.hash_password(password):
return False

return True

def hash_password(self, password):
# 简化示例,实际应使用 bcrypt 等
return f"hashed_{password}"

# 单元测试(使用 Mock)
from unittest.mock import Mock

def test_login_success():
"""测试登录成功"""
# 创建 Mock 数据库
mock_db = Mock()
mock_user = Mock()
mock_user.password = "hashed_password123"
mock_db.find_user_by_email.return_value = mock_user

service = UserService(mock_db)
result = service.login("user@example.com", "password123")

assert result == True
mock_db.find_user_by_email.assert_called_once_with("user@example.com")

def test_login_user_not_found():
"""测试用户不存在"""
mock_db = Mock()
mock_db.find_user_by_email.return_value = None

service = UserService(mock_db)
result = service.login("nobody@example.com", "password123")

assert result == False

def test_login_wrong_password():
"""测试密码错误"""
mock_db = Mock()
mock_user = Mock()
mock_user.password = "hashed_correct_password"
mock_db.find_user_by_email.return_value = mock_user

service = UserService(mock_db)
result = service.login("user@example.com", "wrong_password")

assert result == False

🚨 常见错误与避坑指南

错误 1:测试实现细节而非行为

错误做法

1
2
3
4
5
def test_user_service():
service = UserService()
# 测试内部实现细节
assert service._database_connection is not None
assert service._cache_enabled == True

正确做法

1
2
3
4
5
def test_user_service():
service = UserService()
# 测试行为
user = service.get_user(123)
assert user.name == "张三"

为什么:测试实现细节会导致测试脆弱,代码重构时测试就会失败。


错误 2:测试间有依赖

错误做法

1
2
3
4
5
6
7
8
user_id = None

def test_create_user():
global user_id
user_id = create_user("张三")

def test_delete_user():
delete_user(user_id) # 依赖上一个测试

正确做法

1
2
3
4
5
6
7
8
def test_create_user():
user_id = create_user("张三")
assert user_id is not None

def test_delete_user():
user_id = create_user("张三") # 自己创建数据
result = delete_user(user_id)
assert result == True

错误 3:测试覆盖率追求 100%

错误做法

为了达到 100% 覆盖率,测试所有 getter/setter、简单的工具函数。

正确做法

  • 优先测试核心业务逻辑
  • 优先测试复杂算法
  • 优先测试容易出错的地方
  • 简单的 getter/setter 可以不测试

行业标准

  • Google:核心代码 80%+,工具代码 60%+
  • 覆盖率不是目的,发现 bug 才是

错误 4:过度使用 Mock

错误做法

1
2
3
4
def test_calculate_total():
mock_add = Mock(return_value=5)
mock_multiply = Mock(return_value=10)
# 过度 Mock,测试失去意义

正确做法

只 Mock 外部依赖(数据库、网络、文件系统),不 Mock 业务逻辑。


📊 总结对比

维度 说明 建议
适用范围 函数、方法、类 优先测试核心业务逻辑
学习成本 低-中 从简单的测试开始
实施难度 低-中 使用成熟的测试框架
ROI 极高 早期投入 20%,减少 80% bug 修复时间
覆盖率目标 70-80% 不追求 100%
运行频率 每次代码修改 集成到 CI/CD

🎓 学习资源

推荐书籍

  • 📚 《单元测试的艺术》(The Art of Unit Testing)- Roy Osherove
  • 📚 《测试驱动开发》(Test-Driven Development)- Kent Beck
  • 📚 《重构:改善既有代码的设计》(Refactoring)- Martin Fowler

在线课程

官方文档


🔗 相关主题

  • [[集成测试]] - 测试多个模块的交互
  • [[端到端测试]] - 测试完整的用户流程
  • [[测试驱动开发(TDD)]] - 先写测试再写代码
  • [[持续集成(CI)]] - 自动化测试流程
  • [[代码覆盖率]] - 衡量测试完整性

💡 快速参考卡片

何时写单元测试?

应该写

  • 核心业务逻辑
  • 复杂算法
  • 容易出错的地方
  • 修复 bug 后(防止回归)

可以不写

  • 简单的 getter/setter
  • 第三方库的代码
  • 配置文件
  • UI 布局代码

一个好的单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def test_calculate_discount():
"""
✅ 测试名称清晰
✅ 只测试一个功能点
✅ 快速(< 10ms)
✅ 独立(不依赖其他测试)
✅ 可重复(每次结果一致)
"""
# Arrange(准备)
price = 100
discount_rate = 0.2

# Act(执行)
result = calculate_discount(price, discount_rate)

# Assert(断言)
assert result == 80

测试金字塔

1
2
3
4
5
6
7
      /\
/E2E\ 10% - 端到端测试(慢、脆弱)
/------\
/集成测试 \ 20% - 集成测试(中速)
/----------\
/ 单元测试 \ 70% - 单元测试(快、稳定)
/--------------\

原则:单元测试占大头,越往上越少。