Python 单元测试实战:pytest 核心用法与工程实践

举报
yd_268144123 发表于 2026/11/10 09:49:52 2026/11/10
【摘要】 一、为什么写单元测试单元测试是保证代码质量的“安全网”:重构时能快速发现回归、定位问题时能缩小范围、写测试的过程本身也会倒逼接口设计更清晰。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。

八、工程实践建议

  1. 测试命名清晰:test_<行为>_<条件>_<预期>,读函数名就知道测什么。
  2. 一个测试只断言一件事,失败时定位快。
  3. 测试不依赖执行顺序,各自独立(善用 fixture 准备/清理)。
  4. 不测实现细节,测行为:重构内部实现时测试不该大面积挂。
  5. CI 里跑测试:合并前自动跑 pytest,挡住回归。
  6. 组织目录:tests/ 下按模块镜像源码结构,conftest.py 放共享配置。

九、常见坑

  1. 测试之间有隐式依赖(共用全局状态没清理):导致单独跑过、一起跑挂。
  2. mock 过度:把被测逻辑也 mock 掉,测了个寂寞。
  3. 断言太宽(只 assert 不为空):抓不到真正的错误。
  4. 外部依赖未隔离:测试依赖真实网络/数据库,慢且不稳定。
  5. 忽视随机/时间:涉及时间、随机的逻辑要注入可控值,否则偶发失败。

十、总结

pytest 的核心就是用 assert 写断言、用 fixture 管好坏境、用 parametrize 覆盖多组数据、用 mock 隔离外部依赖。把测试写得独立、清晰、只测行为,再接入 CI,就能用很小成本获得一张可靠的回归安全网。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。