Python 测试利器:pytest 框架介绍

作者:c_chun 发布时间: 2025-09-20 阅读量:4 评论数:0

在 Python 自动化测试领域,测试框架的选择直接影响测试效率与代码可维护性。而 pytest 框架 凭借其灵活的语法、强大的插件生态和丰富的功能,成为了开发者和测试工程师的首选工具。它不仅解决了 Python 内置 unittest 框架语法繁琐、扩展性弱的痛点,还支持单元测试、集成测试、接口测试等多种场景,让 “编写测试用例” 这件事变得高效且优雅。

本文将从基础到进阶,带大家全面掌握 pytest 框架的使用 —— 从环境安装到核心语法,从参数化、夹具到实战案例,再到插件扩展技巧,帮你快速上手并搭建企业级测试体系。

一、为什么选择 pytest 框架?—— 它的核心优势

在学习使用前,我们先明确一个问题:Python 自带 unittest 框架,为什么还要用 pytest?答案藏在它的四大核心优势里:

  1. 语法极简,编写效率高

unittest 需要继承 TestCase 类、使用固定的断言方法(如 self.assertEqual()),而 pytest 无需继承类,直接用函数定义测试用例,断言可使用 Python 原生语法(如 assert a == b),代码量减少 30% 以上。例如,一个简单的测试用例,pytest 只需 3 行代码,而 unittest 至少需要 5 行。

  1. 功能强大,覆盖所有测试场景

支持参数化测试(批量生成用例)、测试夹具(前置 / 后置操作)、跳过用例、标记用例、失败重试等高级功能,无需额外封装工具类。同时,兼容 unittest 和 nose 框架的测试用例,迁移成本极低。

  1. 插件生态丰富,扩展性强

拥有超过 800 个官方及第三方插件,可轻松实现测试报告生成(pytest-html)、接口测试(pytest-requests)、异步代码测试(pytest-asyncio)、与 CI/CD 工具集成(如 Jenkins)等需求,插件安装与使用简单,一行命令即可完成配置。

  1. 输出清晰,调试效率高

测试执行结果会以彩色文本形式展示,清晰标注用例通过 / 失败状态;若用例失败,会详细输出断言对比信息(如预期值与实际值的差异)、错误堆栈跟踪,帮助快速定位问题根源。

二、环境准备:3 分钟完成安装与验证

pytest 是第三方库,需通过包管理工具安装,支持 Windows/macOS/Linux 全平台,且兼容 Python 3.7 及以上版本。

1. 安装方式(推荐 pip)

打开终端 / 命令提示符,输入以下命令,等待安装完成:

pip install pytest

若需要安装指定版本(如适配旧项目),可指定版本号:

pip install pytest==7.4.3  # 安装 7.4.3 版本

2. 验证安装是否成功

安装完成后,在终端输入以下命令,查看 pytest 版本:

pytest --version

若输出类似 pytest 7.4.3 的信息,说明安装成功。

3. 第一个 pytest 测试用例

创建一个名为 test_demo.py 的文件,写入以下代码:

# test_demo.py
def add(a, b):

    """待测试的加法函数"""

    return a + b

def test_add_positive():

    """测试正数相加"""

    result = add(2, 3)

    assert result == 5  # 原生断言,简洁直观

def test_add_negative():

    """测试负数相加"""

    result = add(-2, -3)

    assert result == -5

def test_add_zero():

    """测试与零相加"""

    result = add(0, 5)

    assert result == 5

在终端进入该文件所在目录,执行以下命令运行测试:

pytest test_demo.py -v
  • -v:启用详细输出模式,展示每个用例的执行结果。

执行后,终端会输出彩色结果:

collected 3 items
test_demo.py::test_add_positive PASSED

test_demo.py::test_add_negative PASSED

test_demo.py::test_add_zero PASSED

3 个用例全部通过,说明 pytest 已能正常工作。

三、核心语法:掌握 90% 场景的用法

pytest 的核心是围绕 测试用例编写测试执行控制 设计的,下面我们逐一讲解高频用法。

1. 测试用例的命名规范

pytest 会自动识别符合以下规则的文件和函数 / 类,无需手动指定用例:

  • 测试函数 / 方法:函数名以 test_ 开头(如 test_login_success)。

  • 测试类:类名以 Test 开头(如 TestLogin),且类中不含 init 方法,类内的测试方法以 test_ 开头。

示例:测试类的使用

# test_user.py
class TestUser:

    """用户相关测试类"""

    def test_user_create(self):

        """测试创建用户"""

        user = {"name": "张三", "age": 25}

        assert user["name"] == "张三"

    def test_user_update(self):

        """测试更新用户"""

        user = {"name": "张三", "age": 25}

        user["age"] = 26

        assert user["age"] == 26

执行命令 pytest test_user.py -v,pytest 会自动识别并执行类中的两个测试方法。

2. 断言语法:简洁的原生断言

pytest 支持 Python 所有原生断言语法,无需记忆特殊方法,常用断言场景如下:

断言场景

pytest 断言语法(原生)

unittest 断言语法(对比)

等于

assert a == b

self.assertEqual(a, b)

不等于

assert a != b

self.assertNotEqual(a, b)

大于 / 小于

assert a > b / assert a < b

self.assertGreater(a, b) / self.assertLess(a, b)

包含 / 不包含

assert b in a / assert b not in a

self.assertIn(b, a) / self.assertNotIn(b, a)

布尔值(真 / 假)

assert a / assert not a

self.assertTrue(a) / self.assertFalse(a)

异常断言(关键场景)

with pytest.raises(异常类): 代码块

self.assertRaises(异常类, 函数, 参数)

重点:异常断言示例

当需要测试函数在特定场景下是否抛出预期异常时,可使用 pytest.raises():

# test_exception.py
import pytest

def divide(a, b):

    """待测试的除法函数"""

    if b == 0:

        raise ZeroDivisionError("除数不能为零")

    return a / b

def test_divide_by_zero():

    """测试除数为零的异常场景"""

    # 断言调用 divide(5, 0) 时会抛出 ZeroDivisionError

    with pytest.raises(ZeroDivisionError) as exc_info:

        divide(5, 0)

    # 可选:验证异常信息是否符合预期

    assert "除数不能为零" in str(exc_info.value)

3. 参数化测试:批量生成用例

在测试同一功能的不同输入场景时(如登录的正确密码、错误密码、空密码),无需编写多个重复的测试函数,可使用 @pytest.mark.parametrize 装饰器批量生成用例,大幅减少代码冗余。

示例:登录功能参数化测试

# test_login_param.py
import pytest

def login(username, password):

    """模拟登录函数:用户名和密码都为 "admin" 时返回成功"""

    if username == "admin" and password == "admin":

        return {"status": "success", "message": "登录成功"}

    else:

        return {"status": "fail", "message": "用户名或密码错误"}

# 参数化装饰器:第一个参数是用例参数名(元组),第二个参数是参数值列表(每个元素是一个用例的参数)

@pytest.mark.parametrize("username, password, expected_status, expected_msg", [

    # 用例 1:正确用户名和密码

    ("admin", "admin", "success", "登录成功"),

    # 用例 2:正确用户名,错误密码

    ("admin", "123456", "fail", "用户名或密码错误"),

    # 用例 3:错误用户名,正确密码

    ("test", "admin", "fail", "用户名或密码错误"),

    # 用例 4:空用户名

    ("", "admin", "fail", "用户名或密码错误"),

    # 用例 5:空密码

    ("admin", "", "fail", "用户名或密码错误"),

])

def test_login_parametrize(username, password, expected_status, expected_msg):

    """登录功能参数化测试"""

    result = login(username, password)

    assert result["status"] == expected_status

    assert result["message"] == expected_msg

执行命令 pytest test_login_param.py -v,会看到 5 个用例被自动生成并执行:

collected 5 items
test_login_param.py::test_login_parametrize[admin-admin-success-登录成功] PASSED

test_login_param.py::test_login_parametrize[admin-123456-fail-用户名或密码错误] PASSED

test_login_param.py::test_login_parametrize[test-admin-fail-用户名或密码错误] PASSED

test_login_param.py::test_login_parametrize[-admin-fail-用户名或密码错误] PASSED

test_login_param.py::test_login_parametrize[admin--fail-用户名或密码错误] PASSED

4. 测试夹具(Fixture):灵活的前置 / 后置操作

在测试中,常需要重复执行一些操作(如初始化数据库连接、创建测试用户、清理测试数据),pytest 的 Fixture 功能可替代传统的 setup()/teardown() 方法,支持灵活的作用域控制和依赖注入,是 pytest 的核心亮点之一。

(1)基础 Fixture:定义前置操作

创建一个名为 conftest.py 的文件(pytest 会自动识别该文件中的 Fixture,无需导入),写入以下代码:

# conftest.py
import pytest

@pytest.fixture

def init_database():

    """Fixture:初始化数据库连接(前置操作)"""

    print("\n=== 初始化数据库连接 ===")

    db_conn = "模拟数据库连接对象"  # 实际场景中可替换为真实的数据库连接

    yield db_conn  # yield 之前是前置操作,之后是后置操作

    print("\n=== 关闭数据库连接 ===")  # 测试用例执行完后执行后置操作

在测试用例中,直接将 Fixture 名作为参数传入,即可使用 Fixture 的返回值:

# test_db.py
def test_query_user(init_database):

    """测试查询用户:依赖 init_database Fixture"""

    # init_database 是 Fixture 的返回值(数据库连接对象)

    print(f"使用数据库连接:{init_database}")

    # 模拟查询操作

    user = {"id": 1, "name": "张三"}

    assert user["id"] == 1

def test_add_user(init_database):

    """测试添加用户:依赖 init_database Fixture"""

    print(f"使用数据库连接:{init_database}")

    # 模拟添加操作

    new_user = {"name": "李四"}

    assert new_user["name"] == "李四"

执行命令 pytest test_db.py -s(-s 用于显示打印信息),输出如下:

collected 2 items
test_db.py 

=== 初始化数据库连接 ===

使用数据库连接:模拟数据库连接对象

.

=== 关闭数据库连接 ===
=== 初始化数据库连接 ===

使用数据库连接:模拟数据库连接对象

.

=== 关闭数据库连接 ===

可见,每个用例执行前都会触发 Fixture 的前置操作,执行后触发后置操作,实现了重复操作的复用。

(2)Fixture 作用域:控制生效范围

通过 scope 参数可设置 Fixture 的作用域,减少重复执行次数,提升测试效率。常用作用域如下:

作用域参数

生效范围

执行时机

function(默认)

每个测试函数 / 方法

每个用例执行前前置,执行后后置

class

每个测试类

测试类中所有用例执行前前置,全部执行后后置

module

每个测试文件

测试文件中所有用例执行前前置,全部执行后后置

session

整个测试会话(所有文件)

所有用例执行前前置,全部执行后后置

示例:设置 class 作用域

# conftest.py
@pytest.fixture(scope="class")

def init_class_database():

    """Fixture:class 作用域,每个测试类执行一次"""

    print("\n=== 初始化 class 级数据库连接 ===")

    db_conn = "class 级数据库连接对象"

    yield db_conn

    print("\n=== 关闭 class 级数据库连接 ===")

在测试类中使用该 Fixture:

# test_class_db.py
class TestUserDB:

    def test_query_user(self, init_class_database):

        print(f"class 级连接:{init_class_database}")

        assert True

    def test_add_user(self, init_class_database):

        print(f"class 级连接:{init_class_database}")

        assert True

执行命令 pytest test_class_db.py -s,输出如下:

collected 2 items
test_class_db.py 

=== 初始化 class 级数据库连接 ===

class 级连接:class 级数据库连接对象

.

class 级连接:class 级数据库连接对象

.

=== 关闭 class 级数据库连接 ===

可见,两个用例共用了一次 Fixture 的前置 / 后置操作,减少了重复执行。

5. 用例控制:跳过、标记与失败重试

(1)跳过用例:@pytest.mark.skip

当某些用例暂时不需要执行(如功能未开发完成、环境不支持)时,可使用 @pytest.mark.skip 装饰器跳过,支持添加跳过原因。

# test_skip.py
import pytest

@pytest.mark.skip(reason="功能未开发完成,暂不测试")

def test_unfinished_feature():

    """测试未开发完成的功能"""

    assert False  # 即使断言失败,也不会执行

@pytest.mark.skipif(condition=True, reason="Python 版本小于 3.8,不支持该功能")

def test_python_version():

    """根据条件跳过(如版本判断)"""

    assert True

执行后,跳过的用例会标记为 s(skipped):

collected 2 items
test_skip.py::test_unfinished_feature SKIPPED (功能未开发完成,暂不测试)

test_skip.py::test_python_version SKIPPED (Python 版本小于 3.8,不支持该功能)

(2)标记用例:@pytest.mark.自定义标记

当测试用例数量较多时,可通过自定义标记(如 smoke(冒烟测试)、api(接口测试)、ui(UI 测试))对用例分类,执行时只需指定标记即可筛选用例。

首先,在 pytest.ini 文件(需手动创建,与测试文件同级)中注册标记,避免警告:

# pytest.ini
[pytest]

markers =

    smoke: 冒烟测试用例(核心功能)

    api: 接口测试用例

    ui: UI 测试用例

然后,在测试用例中添加标记:

# test_mark.py
import pytest

@pytest.mark.smoke  # 标记为冒烟测试用例

def test_login_smoke():

    """登录功能冒烟测试(核心用例)"""

    assert True

@pytest.mark.api  # 标记为接口测试用例

def test_user_api():

    """用户接口测试"""</doubaocanvas>

评论