← 返回题目列表

pytest 相比 unittest 有什么优势?常用特性有哪些?

高频 简单 第 2 / 27 题 更新于 2026/07/27
pytestunittest测试框架

简化版

pytest 是 Python 生态里非常常用的测试框架,相比 unittest 写法更简洁、断言更自然、fixture 更强大、插件生态更丰富。它既能运行普通函数式测试,也兼容 unittest 风格测试,适合从小项目到大型工程逐步演进。

详细版

pytest 的优势主要体现在几个方面:

  1. 用普通函数就能写测试,不强制继承 TestCase
  2. 直接使用 Python 的 assert,失败时会展示更详细的表达式差异;
  3. fixture 可以优雅管理测试前置资源和清理逻辑;
  4. 支持参数化测试,减少重复用例;
  5. 插件生态丰富,例如覆盖率、并发执行、Django/FastAPI 测试集成;
  6. 能自动发现 test_*.py*_test.pytest_* 函数或方法;
  7. 可以通过 marker 对测试分组,例如 smoke、slow、integration。

示例:

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

def test_add():
    assert add(1, 2) == 3

面试中可以强调:pytest 不是只让语法变短,它真正提升的是测试组织、复用和工程化能力。

完整版教学

一、为什么 Python 项目常用 pytest

Python 标准库自带 unittest,它是 xUnit 风格框架,和 Java JUnit 很像:测试类继承 unittest.TestCase,通过 self.assertEqualself.assertTrue 等方法断言。它稳定、内置、兼容性好,但写起来相对啰嗦。

pytest 的思路更 Pythonic。测试可以只是一个普通函数,断言可以直接使用 Python 原生 assert

def test_user_name():
    user = {"name": "Tom"}
    assert user["name"] == "Tom"

这降低了写测试的心理成本。测试写得越自然,团队越愿意补测试;团队愿意补测试,项目质量才会真正上去。

二、pytest 的测试发现规则

pytest 默认会从当前目录递归查找测试文件。常见规则是文件名以 test_ 开头或以 _test.py 结尾,测试函数以 test_ 开头,测试类以 Test 开头。

例如:

tests/
  test_user_service.py
  test_order_api.py

文件里可以写:

def test_create_user():
    ...

class TestOrderService:
    def test_create_order(self):
        ...

这套规则让测试结构有明确约定。工程里最好把测试放在 tests/ 目录,按业务模块或层次拆分,不要散落在各处。

三、assert 重写为什么好用

pytest 最让人舒服的地方之一是断言失败信息清楚。比如:

def test_list():
    assert [1, 2, 3] == [1, 2, 4]

pytest 会展示两个列表在哪个位置不同,而不是只告诉你 False is not true。这是因为 pytest 会对 assert 语句做断言重写,记录表达式中间值。

这点看似小,但排查失败用例时很有价值。测试失败不可怕,看不懂失败原因才可怕。

四、fixture 是 pytest 的核心能力

很多测试都需要前置资源,例如数据库连接、临时文件、登录用户、测试客户端。unittest 常用 setUptearDown,pytest 更推荐 fixture:

import pytest

@pytest.fixture
def user():
    return {"id": 1, "name": "Tom"}

def test_user_name(user):
    assert user["name"] == "Tom"

测试函数只要声明参数 user,pytest 就会自动找到同名 fixture 并注入。fixture 还能设置作用域、依赖其他 fixture,并用 yield 做清理。

这让测试前置逻辑可以模块化复用,而不是在每个测试类里重复写初始化代码。

五、参数化测试减少重复

如果一个函数需要测试多组输入输出,pytest 可以用参数化:

import pytest

@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

这样一组逻辑可以跑多组数据。失败时 pytest 会指出是哪一组参数失败。面试里提到参数化,能体现你知道如何避免复制粘贴测试。

六、pytest 和 unittest 的关系

pytest 不是完全替代 unittest 的敌人。它可以运行大部分 unittest 风格测试,所以老项目可以逐步迁移。比如已有:

import unittest

class TestUser(unittest.TestCase):
    def test_name(self):
        self.assertEqual("Tom", "Tom")

pytest 也能发现并执行它。这对存量项目非常友好:团队可以保留旧测试,新测试采用 pytest 风格。

七、常见误区与追问

能力pytest 做法面试要点
断言直接写 assert失败时能展示表达式差异
资源准备fixture 注入比到处写初始化更可复用
多组数据parametrize3 组输入可以复用 1 个测试函数
测试分组marker区分 slow、integration、smoke
一次典型 pytest 执行:
发现 test_*.py -> 解析 fixture -> 执行参数化用例 -> 展示断言差异 -> 汇总失败位置

记忆钩子:pytest 的优势不是“少写几行”,而是让测试发现、资源复用、失败定位和工程扩展更顺手。

第一个误区是只会写 happy path。测试不只验证正常输入,也要覆盖边界、异常、空值、权限失败和外部依赖失败。

第二个误区是测试函数之间相互依赖。测试应该尽量独立,不能假设某个测试先运行,另一个测试再使用它留下的数据。

第三个误区是 fixture 滥用。fixture 太多、层层嵌套,会让测试读起来像谜语。fixture 应该帮助理解,而不是制造隐式魔法。

  • 误区:pytest 只是 unittest 的简写版。 pytest 的核心价值还包括断言重写、fixture、参数化、marker 和插件生态,不只是语法更短。
  • 误区:测试只覆盖正常路径就够了。 面试和工程都要关注边界、异常、空值、权限失败、外部依赖失败,否则测试很难挡住真实线上问题。
  • 误区:fixture 越抽象越专业。 过深的 fixture 链会让测试数据来源不透明,测试允许有少量重复来换取可读性。
  • 追问:pytest 如何兼容 unittest? pytest 可以发现并执行很多 unittest.TestCase 风格的测试,老项目可以渐进迁移,新测试再采用 pytest 风格。
  • 追问:参数化测试适合什么场景? 同一行为要验证多组输入输出时最适合,例如 3 个合法年龄、3 个非法金额、多个状态流转分支。
  • 追问:marker 的工程价值是什么? marker 可以把测试分成 slowintegrationsmoke 等集合,让本地和 CI 按成本分层运行。

八、加强记忆

记 pytest 的核心价值:普通函数写测试,原生 assert 做断言,fixture 管资源,parametrize 管多组数据,插件补工程能力。它的优势不是单纯语法短,而是让测试更容易组织、复用、定位失败并融入工程流程。