一、测试基础理论:从“写代码”到“负责任”

说实话,我刚入行那会儿,对测试的态度就是——能跑就行。直到有一次,我负责的一个支付接口上线后,因为一个边界条件没覆盖,导致用户重复扣款。那晚我盯着日志到凌晨三点,才意识到:测试不是浪费时间,是在给自己留后路

这一章,咱们聊聊测试最核心的几个概念。不扯虚的,全是实战中你一定会碰到的。

1.1 软件测试的定义:你到底在测什么?

官方定义说:软件测试是为了发现错误而执行程序的过程。但我个人更愿意把它理解为——验证你的代码在真实场景下,不会出幺蛾子

你想想看,写一个函数,输入1+1,输出2,这谁都会。但用户可能输入的是字符串、负数、甚至空值。测试要做的,就是把这些“不正经”的输入都试一遍。

核心要点:

  • 测试不是证明程序没错,而是找出现有错误
  • 好的测试能让你放心重构代码
  • 测试覆盖率≠代码质量,但没覆盖率肯定不行

我在项目中遇到过最典型的例子:一个看似简单的用户注册接口,前端传了邮箱、密码、验证码。后端只校验了密码长度,结果有人用SQL注入绕过了验证。嗯,这就是测试没做到位的后果。

1.2 测试金字塔模型:别把鸡蛋放一个篮子里

这个模型是Mike Cohn提出的,说白了就是告诉你:测试应该分层,而且底层要厚实

层级 名称 特点 数量建议
顶层 端到端测试(E2E) 慢、贵、不稳定 少量
中间层 集成测试 验证模块间协作 适量
底层 单元测试 快、稳定、精准定位 大量

我习惯把金字塔倒过来想:如果单元测试写得好,集成测试和E2E测试的压力会小很多。就像盖房子,地基稳了,上面怎么折腾都不怕。

我的经验:别一上来就写E2E测试。我曾经在一个项目里写了200个E2E用例,结果每次CI跑40分钟,还经常因为网络波动挂掉。后来改成80%单元测试+15%集成测试+5%E2E,速度从40分钟降到3分钟。

1.3 单元测试 vs 集成测试:到底有啥区别?

这个问题,面试的时候被问烂了。但很多人其实没搞明白。

单元测试:测一个函数、一个类、一个模块。不依赖外部资源(数据库、网络、文件系统)。
集成测试:测多个模块一起工作时,会不会打架。会用到真实或模拟的外部资源。

举个例子:

# 单元测试:只测计算逻辑
def calculate_discount(price, coupon):
    if coupon == 'VIP':
        return price * 0.8
    return price

# 集成测试:测整个下单流程
def test_create_order():
    user = create_user('test@example.com')
    product = create_product('Python课程', 100)
    order = create_order(user.id, product.id)
    assert order.status == 'pending'
    assert order.total == 80  # 假设VIP打8折

你看,单元测试只关心“输入→输出”对不对。集成测试则要确保数据库写入、缓存更新、消息队列发送都正常。

避坑指南:我曾经犯过一个错误——把集成测试当单元测试写。结果测试里连了真实数据库,每次跑完还要手动清理数据。后来改成用内存数据库+Mock,才把测试速度提上来。

1.4 测试驱动开发(TDD):先写测试再写代码?疯了吗?

我第一次听说TDD时,反应跟你一样:代码都没写,测什么?但后来被一个老同事逼着用了一周,真香。

TDD的核心流程就三步:

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

说白了,就是用测试来驱动你的设计。因为你要先想清楚“这个函数应该输出什么”,才会去写实现。这能逼你写出更清晰、更可测试的代码。

一个真实的TDD例子:

# 第一步:写测试(红)
def test_is_prime():
    assert is_prime(2) == True
    assert is_prime(4) == False
    assert is_prime(17) == True

# 第二步:写最简实现(绿)
def is_prime(n):
    if n < 2:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True

# 第三步:重构(优化性能)
def is_prime(n):
    if n < 2:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    for i in range(3, int(n**0.5) + 1, 2):
        if n % i == 0:
            return False
    return True

嗯,这里要注意:TDD不是银弹。对于探索性代码(比如原型验证),先写测试反而拖慢进度。但对于业务逻辑明确的模块,TDD能帮你减少至少30%的bug。

小结

这一章我们聊了:

  • 测试的本质是找问题,不是证明没问题
  • 测试金字塔告诉我们单元测试要占大头
  • 单元测试和集成测试的核心区别在于依赖
  • TDD是先写测试再写代码,能提升代码质量

下一章,咱们会动手写第一个Python单元测试。到时候你会发现,其实测试没那么难,难的是坚持写。

个人建议:从明天开始,每个新函数都先写一个测试。哪怕只是验证输入输出。坚持两周,你会回来感谢我的。