Pytest 测试框架详细讲解

9 阅读10分钟

1. Pytest 是什么

pytest 是 Python 生态里最常用的测试框架之一,既可以做单元测试,也可以做接口测试、Web 自动化测试、数据驱动测试和测试平台集成。

它的定位不是“只会跑 assert 的工具”,而是一套完整的测试执行框架,主要提供这些能力:

  • 测试用例自动发现
  • 测试执行与结果汇总
  • 断言增强
  • 前后置管理
  • 参数化
  • 插件扩展
  • 测试报告集成

很多团队最终选择 pytest,不是因为它“语法简单”这么单一,而是因为它把“测试组织能力”和“扩展能力”做得非常好。


2. 为什么大家喜欢用 Pytest

相比很多传统测试框架,pytest 有几个很明显的优势。

2.1 写法简单

最基础的测试只需要一个普通函数:

def test_add():
    assert 1 + 1 == 2

不一定非要继承某个基类,也不一定必须写很多样板代码。

2.2 原生支持 assert

pytest 不要求你写很多类似 self.assertEqual() 的方法,而是直接支持 Python 原生 assert

def test_user_name():
    name = "tom"
    assert name == "tom"

同时它会对断言失败信息做增强,方便定位问题。

2.3 fixture 机制灵活

它提供了非常强大的 fixture 机制,适合管理:

  • 前置准备
  • 后置清理
  • 资源共享
  • 依赖注入

2.4 参数化能力强

一套测试逻辑可以轻松跑多组数据。

2.5 插件生态丰富

比如:

  • allure-pytest
  • pytest-xdist
  • pytest-order
  • pytest-rerunfailures
  • pytest-html

这使得它特别适合企业里的测试自动化项目。


3. Pytest 的核心工作流程

从框架视角看,pytest 大致会经历下面几个阶段:

  1. 读取配置
  2. 发现测试用例
  3. 收集测试节点
  4. 解析 fixture
  5. 执行测试s
  6. 记录结果
  7. 触发钩子和插件
  8. 输出报告

也就是说,pytest 不只是“挨个执行测试函数”,它内部其实是一个带收集、调度、注入和扩展机制的执行系统。


4. 测试发现机制

pytest 会根据命名规则自动发现测试文件和测试函数。

常见默认规则:

  • 文件名:test_*.py*_test.py
  • 测试函数:test_*
  • 测试类:Test*

示例:

def test_login():
    assert True


class TestUser:
    def test_create_user(self):
        assert 1 == 1

注意:

  • 测试类通常不需要继承特定父类
  • 测试类里一般不要写自定义 __init__

如果写了不符合规范的名称,pytest 可能不会收集到。


5. 断言机制

pytest 的断言能力是它非常核心的卖点。

5.1 为什么 assert 在 Pytest 里更强

普通 Python 的 assert 断言失败时,通常信息比较少。

pytest 会对断言表达式做重写和增强,所以当断言失败时,它会展示更详细的对比信息。

例如:

def test_compare():
    assert {"code": 500} == {"code": 200}

失败时它会告诉你具体哪个字段不一致。

5.2 常见断言写法

def test_equal():
    assert 200 == 200


def test_contains():
    assert "ok" in "request ok"


def test_not_none():
    result = {"token": "abc"}
    assert result["token"] is not None

5.3 断言建议

  • 断言要尽量明确
  • 一个测试关注一个主要目标
  • 不要把很多不相关断言堆在同一个用例里

6. fixture 机制

fixturepytest 最重要的特性之一。

如果你只把它理解成“前置后置”,其实还不够。更准确地说,它是:

一种测试资源管理和依赖注入机制。

6.1 fixture 的基本用法

import pytest


@pytest.fixture
def login_token():
    return "token_123"


def test_query_user(login_token):
    assert login_token == "token_123"

这里的 login_token fixture 会在测试执行时自动注入到 test_query_user 中。

6.2 fixture 能做什么

它可以用来管理:

  • 登录态
  • 数据库连接
  • 浏览器驱动
  • 测试数据初始化
  • 临时文件
  • mock 对象

6.3 fixture 的本质

测试函数通过“参数名”声明依赖,pytest 根据参数名去找对应 fixture 并注入。

这是一种非常自然的依赖注入方式。

6.4 fixture 的作用域

常见作用域有:

  • function
  • class
  • module
  • package
  • session

示例:

@pytest.fixture(scope="module")
def db_conn():
    return "db"

含义:

  • function:每个测试函数执行一次
  • class:每个测试类执行一次
  • module:每个模块执行一次
  • session:整个测试会话只执行一次

6.5 fixture 的前后置

如果只需要前置:

@pytest.fixture
def data():
    print("初始化")
    return 1

如果既要前置又要后置:

@pytest.fixture
def conn():
    print("连接数据库")
    yield "db_conn"
    print("关闭数据库")

yield 前面的代码是前置,后面的代码是后置。

6.6 autouse 自动生效

@pytest.fixture(autouse=True)
def setup_env():
    print("自动执行")

autouse=True 表示不需要显式传参,自动对当前作用域内的测试生效。

6.7 fixture 可以相互依赖

@pytest.fixture
def user():
    return "tom"


@pytest.fixture
def token(user):
    return f"{user}_token"

这说明 fixture 不仅能给测试函数注入,也能给其他 fixture 注入。


7. fixture 和 setup/teardown 的区别

这是面试高频题。

7.1 setup/teardown

这是更传统的前后置写法,比如:

  • setup_method
  • teardown_method
  • setup_class
  • teardown_class

它比较适合简单场景。

7.2 fixture

相比之下,fixture 更强,因为它:

  • 可复用
  • 可组合
  • 可控制作用域
  • 可依赖注入
  • 可和参数化配合

7.3 面试回答模板

你可以这样说:

setup/teardown 更像传统的固定前后置,而 fixture 是 pytest 推荐的资源管理方式。fixture 不仅能做前后置,还支持作用域控制、依赖注入和共享资源,所以在中大型测试项目里更常用。


8. 参数化 parametrize

参数化可以让一条测试逻辑执行多组数据,这是 pytest 非常常用的能力。

8.1 最基本的参数化

import pytest


@pytest.mark.parametrize("a,b,result", [
    (1, 2, 3),
    (2, 3, 5),
    (3, 4, 7),
])
def test_add(a, b, result):
    assert a + b == result

8.2 参数化的价值

  • 减少重复代码
  • 提高测试覆盖率
  • 方便做数据驱动

8.3 和 fixture 配合

参数化不仅能作用在测试函数,也能和 fixture 一起用。

8.4 常见使用场景

  • 登录接口多组用户名密码
  • 搜索接口多组关键字
  • 边界值测试
  • 不同环境或不同浏览器组合执行

9. mark 标记机制

pytest.mark 可以给测试打标签。

9.1 自定义标记

import pytest


@pytest.mark.smoke
def test_login():
    assert True

然后可以只执行某一类测试:

pytest -m smoke

9.2 常见用途

  • 冒烟测试
  • 回归测试
  • 慢用例
  • 重要模块
  • 指定环境

9.3 跳过和预期失败

import pytest


@pytest.mark.skip(reason="暂时跳过")
def test_skip_case():
    pass


@pytest.mark.xfail(reason="已知缺陷")
def test_known_bug():
    assert 1 == 2

skip 是不执行。

xfail 是允许失败,通常表示“这个问题已知,但暂时不阻塞本次测试”。


10. conftest.py 是什么

conftest.pypytest 的特殊配置文件,用来存放:

  • 公共 fixture
  • hook 钩子
  • 全局测试控制逻辑

它的特点是:

  • 不需要手工导入
  • 会被当前目录及子目录下的测试自动识别

10.1 典型用途

  • 定义共享 fixture
  • 统一登录
  • 初始化数据库
  • 测试开始前清理环境
  • 测试结束后收集结果

10.2 它不是 fixture 本身

这是一个很容易混淆的点。

conftest.py 是“放 fixture 的地方”,真正的 fixture 是里面带 @pytest.fixture 的函数。


11. pytest.ini 配置文件

pytest.inipytest 的配置中心。

常见可配置项:

  • 测试路径
  • 测试文件命名规则
  • marker 注册
  • 默认命令行参数
  • 日志配置

例如:

[pytest]
addopts = -s -v
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
    smoke: smoke test
    regression: regression test

为什么要配置?

因为这样可以统一团队执行规范,避免每个人本地执行方式都不一样。


12. hook 钩子机制

pytest 内部有很多 hook,用来让你在测试执行生命周期的不同阶段插入自定义逻辑。

例如:

  • 测试开始前
  • 测试收集时
  • 每条用例执行前后
  • 测试结束汇总时

典型 hook 示例:

def pytest_terminal_summary(terminalreporter, exitstatus, config):
    print("测试结束,输出汇总信息")

这类能力非常适合:

  • 定制测试结果输出
  • 接入通知
  • 接入测试平台
  • 汇总失败信息

13. Pytest 常用命令

13.1 执行全部测试

pytest

13.2 显示详细信息

pytest -v

13.3 显示 print

pytest -s

13.4 执行指定文件

pytest test_login.py

13.5 执行指定用例

pytest test_login.py::test_login_success

13.6 按关键字过滤

pytest -k login

13.7 按标记执行

pytest -m smoke

13.8 失败后停止

pytest -x

13.9 只跑失败用例

pytest --lf

14. Pytest 常见插件

14.1 allure-pytest

生成更丰富的测试报告。

14.2 pytest-xdist

支持并发执行。

命令示例:

pytest -n 4

14.3 pytest-order

控制测试执行顺序。

14.4 pytest-rerunfailures

失败用例自动重跑。

14.5 pytest-html

生成 HTML 测试报告。

插件生态丰富,是 pytest 在企业环境里非常受欢迎的重要原因。


15. Pytest 和 unittest 的区别

这是高频面试题。

15.1 写法

  • unittest 更偏传统、规范化
  • pytest 更简洁、灵活

15.2 断言

  • unittest 常用 self.assertEqual()
  • pytest 直接用 assert

15.3 前后置

  • unittest 常用 setUp/tearDown
  • pytest 更推荐 fixture

15.4 参数化

  • pytest 原生更方便
  • unittest 做参数化通常没那么自然

15.5 插件生态

pytest 更丰富。

15.6 面试总结回答

unittest 更像标准库里的传统测试框架,结构规范但写法偏重;

pytest 更轻量、灵活,支持更强的 fixture、参数化和插件扩展,所以在自动化测试项目里通常更常见。


16. Pytest 适合什么场景

pytest 并不只适合接口测试,它其实适合大部分 Python 测试场景:

  • 单元测试
  • 接口自动化测试
  • Web UI 自动化测试
  • 中间件测试
  • 数据校验测试
  • 平台级测试集成

如果项目需要:

  • 数据驱动
  • 复杂前后置
  • 报告集成
  • 与平台结合

pytest 往往会是一个很好的选择。


17. Pytest 的优点和局限

17.1 优点

  • 简洁
  • 灵活
  • 可扩展
  • 插件多
  • fixture 强大
  • 参数化方便
  • 断言友好

17.2 局限

  • fixture 用多了,依赖关系可能变复杂
  • 新手在大型项目里容易被 conftest.py、fixture 注入和 hook 搞混
  • 插件太多时,需要统一团队规范

所以 pytest 虽然强大,但在团队里也要注意测试结构设计。


18. 面试高频问题

18.1 什么是 fixture

推荐回答:

fixture 是 pytest 提供的资源管理和依赖注入机制,用来处理前后置逻辑、共享资源和测试依赖。

它比传统的 setup/teardown 更灵活,因为支持作用域、自动生效、fixture 之间依赖以及 yield 形式的清理逻辑。

18.2 conftest.py 有什么用

推荐回答:

conftest.py 是 pytest 的特殊文件,用来存放公共 fixture 和 hook。

它不需要显式导入,当前目录和子目录下的测试会自动识别它。

18.3 Pytest 如何做参数化

推荐回答:

Pytest 通过 @pytest.mark.parametrize 实现参数化,可以把一条测试逻辑和多组测试数据组合起来执行,非常适合数据驱动测试。

18.4 fixture 和 setup/teardown 有什么区别

推荐回答:

setup/teardown 更偏传统前后置,能力相对固定;

fixture 不仅能做前后置,还支持依赖注入、作用域控制和共享资源,扩展性更强。

18.5 为什么 Pytest 适合自动化测试

推荐回答:

因为它支持测试发现、参数化、fixture、hook 和丰富插件,能够很好地和 requests、selenium、allure、数据库、CI 平台结合,所以非常适合自动化测试框架建设。


19. 学习 Pytest 时最应该掌握什么

如果要真正学会 pytest,建议优先掌握这几块:

  1. 测试发现规则
  2. assert 断言
  3. fixture
  4. parametrize
  5. conftest.py
  6. pytest.ini
  7. 常用命令
  8. 常用插件

这几块掌握了,已经可以覆盖绝大多数实际工作场景。


20. 一句话总结

pytest 不是“一个能跑测试函数的小工具”,而是一套围绕测试发现、依赖注入、参数化、生命周期控制和插件扩展构建起来的完整测试框架。

它之所以流行,是因为它既适合写简单测试,也足够强大,能支撑复杂的自动化测试工程。