进阶 #software-eng#testing

软件测试(单元/集成/E2E)

软件测试是保证代码质量的核心手段——单元测试测函数,集成测试测模块协作,E2E 测试测完整业务流程

🔍 写代码不测试——就像考完不检查

你写完了系统的”登录功能”,觉得没问题就上线了。结果第二天,100 个用户无法登录——因为密码含特殊字符时验证逻辑出错了。

测试的价值:不是”测出 Bug”,而是在用户发现 Bug 之前自己发现它

🏪 类比:质检流水线

一家奶茶店”>

  • 不做测试:做好直接给客人——“这杯太甜了""这杯没有珍珠”→ 客户体验极差
  • 单元测试:每一杯做好后,店员自己尝一口(单独验证每个功能)
  • 集成测试:两杯同时做,看流程能不能配合好(验证模块间协作)
  • E2E 测试:模拟一个完整流程——“点单→付钱→取奶茶→喝完”(端到端验证)

🏔️ 测试金字塔

最经典的测试策略是测试金字塔

      /\           E2E 测试(少而贵)
     /  \          端到端测试:模拟用户完整操作
    /    \
   /      \
  /________\      集成测试(中)
 /          \      测试模块之间协作
/____________\
|  单元测试(多而快) |
|  测试单个函数/方法  |
|____________________|

核心原则:底层(单元测试)多写,上层(E2E)少写。

① 单元测试(Unit Test)——测试最小的”零件”

测什么:单个函数、单个方法——隔离测试。

# 待测试的函数
def calculate_grade(score):
    if score >= 90: return "优秀"
    if score >= 80: return "良好"
    if score >= 70: return "中等"
    if score >= 60: return "及格"
    return "不及格"

# 单元测试
def test_calculate_grade():
    assert calculate_grade(95) == "优秀"
    assert calculate_grade(85) == "良好"
    assert calculate_grade(75) == "中等"
    assert calculate_grade(65) == "及格"
    assert calculate_grade(55) == "不及格"
    assert calculate_grade(100) == "优秀"  # 边界值
    assert calculate_grade(59) == "不及格"  # 边界值
    assert calculate_grade(0) == "不及格"   # 边界值

特点:运行快(毫秒级)、定位精确(哪个函数出错一目了然)、容易写。

覆盖率(Coverage):衡量”有多少代码被测试覆盖”。

# 用 pytest 跑测试看覆盖率
pytest --cov=myapp tests/
# 输出:Name           Stmts   Miss  Cover
#       grade.py          10      1    90%

好目标:核心逻辑的单元测试覆盖 > 80%。但不要追求 100%——有些代码(如 UI 渲染)不值得单元测试。

② 集成测试(Integration Test)——测试零件能不能组装好

测什么:多个模块一起工作——数据库 + 业务逻辑 + API。

def test_create_user_integration():
    # 测试"创建用户"的全流程:API → 业务层 → 数据库
    response = client.post("/api/users", json={
        "name": "张三",
        "email": "zhangsan@example.com"
    })
    assert response.status_code == 201
    
    # 验证数据确实写入了数据库
    user = db.session.query(User).filter_by(email="zhangsan@example.com").first()
    assert user is not None
    assert user.name == "张三"

特点:比单元测试慢(涉及真实数据库/网络)、能发现”模块间接口不匹配”的问题。

③ E2E 测试(End-to-End Test)——模拟真实用户

测什么:从用户界面操作到后台服务——完整业务流程。

# E2E 测试——用 Selenium 模拟浏览器操作
def test_user_checkout():
    driver = webdriver.Chrome()
    driver.get("https://shop.example.com")
    driver.find_element(By.ID, "username").send_keys("张三")
    driver.find_element(By.ID, "password").send_keys("123456")
    driver.find_element(By.ID, "login-btn").click()
    driver.find_element(By.ID, "add-to-cart").click()
    driver.find_element(By.ID, "checkout").click()
    
    assert "下单成功" in driver.page_source
    driver.quit()

特点:最接近真实用户体验、最慢(可能几分钟)、最脆弱(UI 变一点点就失败)。


🧪 TDD(测试驱动开发)——先写测试再写代码

TDD 的工作流程是”红-绿-重构”循环:

1. 红:先写一个会失败的测试(还没写功能代码)
2. 绿:写最简代码让测试通过
3. 重构:优化代码,保持测试通过

每次循环只加一个功能点。

示例:
# 第 1 步:先写测试(会失败)
def test_is_even():
    assert is_even(4) == True
    assert is_even(3) == False

# 第 2 步:写最简实现让测试通过
def is_even(n):
    return n % 2 == 0

# 第 3 步:重构(这里太简单了,不需要重构)
# 然后循环:再加一个测试(比如处理负数)...

💡 TDD 的价值

  • 写代码前就想清楚”到底要什么”(测试就是需求)
  • 自然的代码设计(因为测试要求代码是可测的 → 自然就解耦了)
  • 安全网(改代码时,测试告诉你”有没有改坏”)

📋 测试策略选择

类型数量速度维护成本定位问题
单元测试⚡ 毫秒精确到函数
集成测试⏱ 秒模块间接口
E2E 测试🐢 分钟全流程

建议比例(Google 推荐的 70/20/10):

  • 70% 单元测试
  • 20% 集成测试
  • 10% E2E 测试

📝 小结

概念一句话
单元测试测单个函数——快、多、精确
集成测试测模块间协作——确保零件能组装
E2E 测试测完整流程——模拟真实用户
测试金字塔多写单元测试、少写 E2E
TDD先写测试再写代码——红绿重构循环
覆盖率衡量测试覆盖了多少代码——核心逻辑 > 80%
边界值0、空值、极限值——Bug 最容易出现在边界

🎯 小练习:为之前的”计算学生成绩等级”函数写完整的 TDD 测试——包括正常值、边界值、异常输入(负数、超过 100 的分数等)。

为什么先学这个? 测试是质量保障的第一步。但要持续保障质量,需要把测试自动化——CI/CD 与 DevOps