编写第一个单元测试:断言机制、测试用例结构、执行与结果

好,咱们正式动手写第一个单元测试。这章我带你从零搭一个最简单的测试,把断言、用例结构、运行结果这三个核心点吃透。你可能会觉得“不就写个assert嘛”,但实际项目里,断言的选型、用例的编排、结果的解读,坑并不少。

断言机制 —— 测试的灵魂

断言(assert)说白了就是“拍胸脯保证”:我断言这个值等于那个值,如果不对,测试就红。Python 的 unittest 内置了一整套断言方法,我习惯把它们分成三类:

  • 值断言:判断相等、不等、是否为空
  • 布尔断言:判断真假
  • 异常断言:判断是否抛出了期望的异常
断言方法 作用 我用它做什么
assertEqual(a, b) 检查 a == b 验证函数返回值是否和预期一致
assertTrue(x) 检查 x 为 True 判断条件是否成立,比如列表是否为空
assertRaises(SomeException, func, arg) 检查调用 func(arg) 时抛出 SomeException 测试异常处理逻辑,我在这上面吃过亏,一会说
小技巧:assertEqual 在比较浮点数时一定用 assertAlmostEqual,否则你可能会被精度问题烦死。你想想,0.1 + 0.2 在计算机里根本不是 0.3。

还记得我第一次写单元测试,直接用 assertEqual(result, 0.3),结果死活不通过。后来才发现浮点比较需要容差。这个坑我记忆犹新。

测试用例结构 —— 四步法

一个规范的测试用例,我建议按以下结构组织:

  1. 准备(Setup):创建测试所需的对象、数据
  2. 执行(Exercise):调用被测方法
  3. 断言(Assert):验证结果
  4. 清理(Teardown):释放资源(可选)

看看这个实际的例子,我们测试一个简单的计算器加法:

import unittest

class TestCalculator(unittest.TestCase):
    def setUp(self):
        # 准备:创建一个计算器实例
        self.calc = Calculator()

    def test_add_two_positive_numbers(self):
        # 执行:调用 add 方法
        result = self.calc.add(3, 5)
        # 断言:期望结果是 8
        self.assertEqual(result, 8)

    def tearDown(self):
        # 清理:这里不需要,但习惯性写上
        pass

if __name__ == '__main__':
    unittest.main()

你发现没有,setUp 和 tearDown 会被每个测试方法自动调用。这样做的好处是每个测试都是独立的,互不干扰。我曾经在项目里见过有人在 setUp 里初始化数据库连接,结果测试跑完后连接没关闭,导致后续测试超时——tearDown 就该干这个事。

注意:不要在测试方法里写 if __name__ == '__main__' 以外的全局代码,否则测试加载时可能产生副作用。曾经有个同事把数据库迁移脚本写在测试文件顶层,每次运行测试都重新建表,数据全没了……嗯,教训。

执行与结果解析

测试写好了,怎么跑?通常两种方式:

  • 命令行:python -m unittest test_module.py
  • IDE 按钮:PyCharm 等集成环境直接点绿色三角形

运行后会看到类似这样的输出:

...
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

. 表示一个测试通过,F 表示失败,E 表示出错(代码异常)。如果看到 FAILED (failures=1),说明断言没通过。我处理这类问题的习惯是:先看是哪一行断言失败,再对比实际值和期望值。

你看,就这么简单。但实际项目里,测试结果可能上百条,你需要学会快速定位。我通常会加上 -v 参数(verbose),它会打印每个测试的名字和结果:

python -m unittest -v test_calculator.py

输出变成:

test_add_negative_numbers (test_calculator.TestCalculator) ... ok
test_add_two_positive_numbers (test_calculator.TestCalculator) ... ok
test_add_zero (test_calculator.TestCalculator) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.002s

OK

这就能一眼看到哪个测试通过了。如果你看到 FAIL,后面会跟着具体的断言失败信息,比如“3 != 5”,定位问题非常快。

核心总结:断言是测试的裁判,用例结构是测试的骨架,执行结果是测试的成绩单。三者缺一不可。
单元测试执行流程与断言结构 1. 准备 (Setup) 创建数据/对象 2. 执行 (Exercise) 调用被测函数 3. 断言 (Assert) assertEqual / assertRaises 4. 清理 (Teardown) 释放资源 测试结果 OK / FAIL / ERROR 分组结构:左侧为准备与清理(生命周期方法),中间为执行与断言(核心逻辑), 右侧为结果汇总。每个测试用例都按此流程独立运行。