人人都会AI编程

18.4 unittest /pytest:单元测试基础

更新时间:2026-07-12

单元测试是保障代码质量的底线——它让你在修改代码后能快速验证核心逻辑是否仍然正确。Python 标准库自带 unittest,而社区首选 pytest 则用更简洁的写法实现了更强的功能。这一节就讲清楚两者的基本用法和实际中怎么选。

unittest:标准库中的测试框架

unittest 的灵感来自 Java 的 JUnit,采用面向对象的方式组织测试。

基本结构

  • 继承 unittest.TestCase
  • 测试方法名必须以 test_ 开头
  • 通过 assertEqualassertTrue 等方法做断言
  • setUp / tearDown 在每个测试前后执行,用于准备和清理环境

让一个例子说话:

import unittest

def add(a, b):
    return a + b

class TestAdd(unittest.TestCase):
    def test_add_positive(self):
        self.assertEqual(add(1, 2), 3)

    def test_add_negative(self):
        self.assertEqual(add(-1, -1), -2)

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

运行:python test_add.pypython -m unittest test_add.py

优点:无需安装,Python 自带,适合对第三方库零依赖要求的场景。
局限:写法模板化,断言方法名冗长,测试发现和参数化支持较弱。

pytest:现代、简洁、强大的测试框架

pytest 是目前 Python 社区事实上的标准测试工具,用简单的函数和 assert 就能写测试,同时拥有强大的插件体系。

基本结构

  • 测试文件和函数名都以 test_ 开头(或 _test 结尾)
  • 直接用内置的 assert 表达式做断言,失败时会自动展示详细对比信息
  • 提供 fixture 机制管理前置条件和公共资源
  • 支持参数化测试,一行装饰器覆盖多组用例

改写上面的例子:

# 无需继承类,无需导入 unittest
def test_add_positive():
    assert add(1, 2) == 3

def test_add_negative():
    assert add(-1, -1) == -2

运行:pytest test_add.py

核心实用特性

  1. 断言内省:当 assert add(1, 2) == 4 失败时,pytest 会明确告诉你 assert 3 == 4,而不像 unittest 只抛出 AssertionError
  2. fixture 管理:用 @pytest.fixture 创建可复用的前置资源(数据库连接、临时文件、测试数据等)。
   import pytest

   @pytest.fixture
   def numbers():
       return [1, 2, 3]

   def test_sum(numbers):
       assert sum(numbers) == 6
   
  1. 参数化测试:轻松用多组数据测试同一逻辑。
   @pytest.mark.parametrize("a,b,expected", [
       (1, 2, 3),
       (0, 0, 0),
       (-1, 1, 0),
   ])
   def test_add(a, b, expected):
       assert add(a, b) == expected
   
  1. 丰富的插件pytest-cov 统计覆盖率,pytest-mock 简化 mock,pytest-django 集成 Django 测试等。

实际中怎么选

  • 从零开始的新项目,直接选 pytest。它的写法更自然,功能更多,效率更高,是当前行业主流。
  • 如果接手的老项目已大量使用 unittest,或者运行环境不能安装第三方库,继续用 unittest 也没问题。两者可以在同一个项目中混用:pytest 可以直接运行 unittest 写的测试用例。

单元测试的基本原则(不论用哪个框架)

  • 测试粒度要小:一个测试只验证一个行为。
  • 隔离性:测试之间不互相依赖,不依赖执行顺序。
  • 可重复:每次运行结果一致,不依赖外部状态(网络、数据库等,必要时通过 mock 或 fixture 控制)。
  • 命名有意义:测试函数名描述清楚“测了什么、在什么条件下、期望什么结果”,例如 test_add_with_two_negatives_returns_negative

掌握了这些,你就可以在日常开发中建立起“改代码 → 跑测试 → 看绿灯”的安全感循环。