Python 单元测试实战:pytest 核心用法与工程实践
【摘要】 一、为什么写单元测试单元测试是保证代码质量的“安全网”:重构时能快速发现回归、定位问题时能缩小范围、写测试的过程本身也会倒逼接口设计更清晰。Python 生态里最主流的框架是 pytest,语法简洁、生态丰富。本文从零讲清 pytest 的核心用法、常用技巧和工程实践。 二、第一个测试# calc.pydef add(a, b): return a + b# test_calc.py...
一、为什么写单元测试
单元测试是保证代码质量的“安全网”:重构时能快速发现回归、定位问题时能缩小范围、写测试的过程本身也会倒逼接口设计更清晰。Python 生态里最主流的框架是 pytest,语法简洁、生态丰富。
本文从零讲清 pytest 的核心用法、常用技巧和工程实践。
二、第一个测试
# calc.py
def add(a, b):
return a + b
# test_calc.py
from calc import add
def test_add():
assert add(1, 2) == 3
def test_add_negative():
assert add(-1, 1) == 0
运行:
pip install pytest
pytest # 自动发现 test_*.py / *_test.py 里的 test_ 函数
pytest -v # 详细输出
pytest 用原生 assert,失败时会自动展示左右值,无需记住一堆 assertXxx 方法。
三、fixture:优雅地准备/清理环境
fixture 用来提供测试所需的依赖(数据库连接、临时文件、样本数据等),并在用完后自动清理:
import pytest
@pytest.fixture
def sample_user():
print("准备数据")
user = {"id": 1, "name": "Tom"}
yield user # yield 之前是准备,之后是清理
print("清理数据")
def test_user_name(sample_user):
assert sample_user["name"] == "Tom"
常用技巧:
scope="session"/"module"/"function"控制 fixture 生命周期。tmp_path内置 fixture 提供临时目录;monkeypatch用于临时改环境变量/属性。conftest.py放共享 fixture,整个目录自动可用。
四、参数化:一份用例测多组数据
import pytest
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
(10, -5, 5),
])
def test_add_param(a, b, expected):
assert add(a, b) == expected
无需手写多个函数,失败时会清晰指出是哪一组数据挂了。
五、断言异常与标记
import pytest
def divide(a, b):
if b == 0:
raise ValueError("分母不能为 0")
return a / b
def test_divide_zero():
with pytest.raises(ValueError):
divide(1, 0)
@pytest.mark.skip(reason="暂未实现")
def test_todo():
...
@pytest.mark.slow
def test_heavy():
...
用 pytest -m "not slow" 可跳过慢用例,加快本地反馈。
六、mock 隔离外部依赖
单元测试应只测“自己的逻辑”,外部依赖(网络、数据库、第三方 API)用 mock 替代:
from unittest.mock import patch
def get_weather(client, city):
resp = client.get(f"/weather/{city}")
return resp["temp"]
def test_get_weather():
fake_client = type("C", (), {})()
fake_client.get = lambda url: {"temp": 25}
assert get_weather(fake_client, "shanghai") == 25
或用 pytest-mock 的 mocker.patch 更简洁。原则:隔离不稳定因素,只断言被测逻辑。
七、覆盖率
pip install pytest-cov
pytest --cov=myapp --cov-report=term-missing
覆盖率能发现“没被测试到的分支”,但别盲目追求 100%——重点覆盖核心业务与边界条件,而不是为了数字去测无意义的 getter。
八、工程实践建议
- 测试命名清晰:
test_<行为>_<条件>_<预期>,读函数名就知道测什么。 - 一个测试只断言一件事,失败时定位快。
- 测试不依赖执行顺序,各自独立(善用 fixture 准备/清理)。
- 不测实现细节,测行为:重构内部实现时测试不该大面积挂。
- CI 里跑测试:合并前自动跑
pytest,挡住回归。 - 组织目录:
tests/下按模块镜像源码结构,conftest.py放共享配置。
九、常见坑
- 测试之间有隐式依赖(共用全局状态没清理):导致单独跑过、一起跑挂。
- mock 过度:把被测逻辑也 mock 掉,测了个寂寞。
- 断言太宽(只 assert 不为空):抓不到真正的错误。
- 外部依赖未隔离:测试依赖真实网络/数据库,慢且不稳定。
- 忽视随机/时间:涉及时间、随机的逻辑要注入可控值,否则偶发失败。
十、总结
pytest 的核心就是用 assert 写断言、用 fixture 管好坏境、用 parametrize 覆盖多组数据、用 mock 隔离外部依赖。把测试写得独立、清晰、只测行为,再接入 CI,就能用很小成本获得一张可靠的回归安全网。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)