Python 单元测试的基本设计思路

8次阅读

单元测试应验证单个函数或方法在隔离环境下的行为,需mock外部依赖;避免测流程、ui或真实I/O;命名用test_[功能]_[场景]_[预期];优先pytest+原生assert;断言要具体到关键字段。

Python 单元测试的基本设计思路

单元测试该测什么,不该测什么

单元测试的核心是验证单个函数或方法在隔离环境下的行为是否符合预期,不是用来测整个流程、外部依赖或 UI 交互的。如果一个函数调用了 requests.get、读了文件、连了数据库,那就先得用 mockpatch 把这些依赖掐掉。

常见误操作包括:

  • 在测试里写 assert response.status_code == 200 —— 这属于集成测试范畴,除非你明确在测 http 客户端封装
  • 测试中创建真实数据库连接并执行 INSERT —— 应该用内存 sqliteunittest.mock 替换 ORM 的 save() 方法
  • 给一个纯计算函数(比如 calculate_tax(amount, rate))写一边界 case 却漏掉 amount=0 或负数 —— 数值类函数必须覆盖零值、负值、极大值、None 和类型错误

如何组织 test_*.py 文件和测试方法名

pythonunittestpytest 都默认识别以 test_ 开头的文件和方法。命名不是随便加前缀,而是要能一眼看出“被测对象 + 场景 + 期望结果”。

推荐格式:test_[function_name]_[scenario]_[expected_behavior],例如:

立即学习Python免费学习笔记(深入)”;

test_calculate_tax_with_zero_amount_returns_zero
test_parse_json_invalid_input_raises_ValueError
test_user_login_success_returns_token

这样在 CI 失败时,光看测试名就能定位问题模块和触发条件,不用点开代码。避免使用 test_case1test_main 这类无法传达语义的名字。

setup/teardown 是不是每次都要写

不是。只有当多个测试方法共享相同初始化逻辑(比如创建临时目录、预置测试数据)且成本较高时,才考虑用 setUp() / tearDown()unittest)或 @pytest.fixturepytest)。否则,宁可每个测试里单独构造输入数据。

原因很实际:

  • 共享状态容易引发测试间污染 —— 某个测试改了全局变量或缓存,下一个测试就莫名其妙失败
  • 调试困难 —— 错误堆里看不出是 setup 还是测试体本身出的问题
  • setUp() 里抛异常会导致整组测试跳过,掩盖真正要测的分支逻辑

简单场景下,直接在测试方法开头写 data = {"name": "test", "age": 25} 更清晰、更可控。

断言该用 assert 还是 self.assertEqual

assert 语句就行,但前提是用 pytest。它会自动重写 assert 表达式,提供清晰的失败信息(比如告诉你 a=3 而不是只报 AssertionError)。而 unittest.TestCaseself.assertEqual(a, b) 在老项目里仍常见,但它对复杂嵌套结构(如 dict 差异)输出极不友好。

实操建议:

  • 新项目统一用 pytest + 原生 assert,配合 pytest-asyncio异步函数
  • 如果必须用 unittest,对字典比较别只写 self.assertEqual(d1, d2),改用 self.assertDictEqual(d1, d2),它会指出具体哪个 key 不同
  • 不要用 assert True == some_func(),直接写 assert some_func();也不要用 assert not result 代替 assert result is False,后者语义更精确

最常被忽略的一点:断言要贴近被测逻辑的粒度。比如函数返回一个对象,别只断言 isinstance(ret, User),而应检查 ret.nameret.is_active 等关键字段是否按预期赋值。

text=ZqhQzanResources