Python常用自动化测试框架——Pytest

Python 常用自动化测试框架——Pytest

一、Pytest 简介

Pytest 是 Python 生态中最流行的第三方测试框架,相比标准库 unittest,它具有以下特点:

  • 语法简洁:使用普通函数编写测试,无需继承测试类
  • 断言原生:直接使用 assert,无需 self.assertEqual()
  • 插件丰富:内置参数化、fixture、标记等功能,社区插件生态完善
  • 兼容 unittest:可以运行已有的 unittest 用例

安装:

pip install pytest

二、快速上手

2.1 基本示例

# test_example.py
def test_addition():
    assert 1 + 1 == 2

def test_string_upper():
    assert "hello".upper() == "HELLO"

运行测试:

pytest test_example.py

2.2 运行方式

# 运行当前目录下所有测试文件
pytest

# 运行指定文件
pytest test_example.py

# 运行指定函数
pytest test_example.py::test_addition

# 运行指定类中的方法
pytest test_example.py::TestClass::test_method

# 显示详细输出
pytest -v

# 显示 print 输出
pytest -s

三、断言

Pytest 直接使用 Python 原生 assert 语句,失败时会自动展示详细的差异信息。

def test_list_containment():
    fruits = ["apple", "banana", "cherry"]
    assert "banana" in fruits

def test_exception():
    # 使用 pytest.raises 检查是否抛出指定异常
    with pytest.raises(ValueError):
        int("invalid")

    # 同时检查异常信息
    with pytest.raises(ValueError, match="invalid literal"):
        int("abc")

常用断言写法:

assert a == b          # 相等
assert a != b          # 不等
assert a in b          # 包含
assert a is True       # 布尔值
assert a is None       # None 检查
assert len(a) == 3     # 长度

四、Fixture——测试前置/后置

Fixture 是 Pytest 的核心机制,用于管理测试的 setup 和 teardown。

4.1 基本用法

import pytest

@pytest.fixture
def sample_data():
    """每个使用该 fixture 的测试都会独立调用"""
    data = {"name": "Alice", "age": 30}
    yield data  # yield 之前的代码是 setup,之后是 teardown
    # yield 之后的代码在测试结束后执行(teardown)

def test_name(sample_data):
    assert sample_data["name"] == "Alice"

def test_age(sample_data):
    assert sample_data["age"] == 30

4.2 Fixture 作用域

通过 scope 参数控制 fixture 的生命周期:

scope 值说明
function默认值,每个测试函数执行一次
class每个测试类执行一次
module每个 .py 文件执行一次
package每个包执行一次
session整个测试会话只执行一次
@pytest.fixture(scope="session")
def db_connection():
    conn = create_connection()
    yield conn
    conn.close()

4.3 conftest.py——共享 Fixture

在项目目录下创建 conftest.py,其中定义的 fixture 会被同目录及子目录下的所有测试自动发现:

tests/
├── conftest.py          # 公共 fixture
├── test_user.py
└── api/
    ├── conftest.py      # api 模块专用 fixture
    └── test_order.py

4.4 Fixture 依赖

@pytest.fixture
def database():
    return Database()

@pytest.fixture
def user_table(database):
    db = database
    db.create_table("users")
    yield db.get_table("users")
    db.drop_table("users")

def test_insert(user_table):
    user_table.insert({"id": 1, "name": "Alice"})
    assert user_table.count() == 1

五、参数化测试

使用 @pytest.mark.parametrize 对同一测试逻辑传入多组数据:

import pytest

@pytest.mark.parametrize("input, expected", [
    (1, 2),
    (2, 3),
    (10, 11),
    (-1, 0),
])
def test_increment(input, expected):
    assert input + 1 == expected

多参数组合(笛卡尔积):

@pytest.mark.parametrize("x", [1, 2])
@pytest.mark.parametrize("y", [10, 20])
def test_multiply(x, y):
    assert x * y > 0
# 会生成 4 个测试用例: (1,10), (1,20), (2,10), (2,20)

六、标记(Mark)

使用标记对测试进行分类和筛选。

6.1 内置标记

import pytest

@pytest.mark.skip(reason="暂未实现")
def test_not_implemented():
    pass

@pytest.mark.skipif(sys.version_info < (3, 10), reason="需要 Python 3.10+")
def test_new_syntax():
    pass

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

6.2 自定义标记

首先在 pytest.inipyproject.toml 中注册标记:

# pytest.ini
[pytest]
markers =
    slow: 耗时较长的测试
    smoke: 冒烟测试

使用自定义标记:

@pytest.mark.slow
def test_large_data_processing():
    ...

@pytest.mark.smoke
def test_basic_flow():
    ...

按标记筛选运行:

pytest -m slow              # 只运行标记为 slow 的测试
pytest -m "slow and smoke"  # 同时标记
pytest -m "not slow"        # 排除 slow

七、测试报告

7.1 终端报告

pytest -v                    # 详细模式
pytest --tb=short           # 简短回溯
pytest --tb=line            # 单行回溯
pytest -q                   # 安静模式
pytest --failed-first       # 失败优先显示

7.2 生成 HTML 报告

pip install pytest-html
pytest --html=report.html --self-contained-html

7.3 生成 JUnit XML(CI/CD 集成)

pytest --junitxml=report.xml

八、配置文件

Pytest 支持多种配置文件(按优先级):

  1. pytest.ini
  2. pyproject.toml
  3. tox.ini
  4. setup.cfg

pytest.ini 示例:

[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short
markers =
    slow: 慢速测试
    smoke: 冒烟测试

九、常用插件

插件用途
pytest-cov测试覆盖率
pytest-htmlHTML 测试报告
pytest-xdist并行执行测试
pytest-mockMock/patch 增强
pytest-timeout超时控制
pytest-asyncio异步测试支持
pytest-seleniumSelenium 自动化测试
pytest-allureAllure 测试报告

覆盖率示例:

pip install pytest-cov
pytest --cov=myapp --cov-report=html

并行执行示例:

pip install pytest-xdist
pytest -n auto    # 自动检测 CPU 核心数并行执行

十、Mock

from unittest.mock import Mock, patch

def test_mock_basic():
    mock_obj = Mock()
    mock_obj.get.return_value = 42
    assert mock_obj.get() == 42
    mock_obj.get.assert_called_once()

def test_with_patch():
    with patch("module.external_api_call") as mock_api:
        mock_api.return_value = {"status": "ok"}
        result = function_under_test()
        assert result["status"] == "ok"
        mock_api.assert_called_once()

配合 pytest-mock 使用更简洁的 fixture:

def test_with_mocker(mocker):
    mocker.patch("module.external_api_call", return_value={"status": "ok"})
    result = function_under_test()
    assert result["status"] == "ok"

十一、实用技巧

11.1 临时目录与文件

def test_with_tmp_path(tmp_path):
    # tmp_path 是 pytest 内置 fixture,提供隔离的临时目录
    file = tmp_path / "data.txt"
    file.write_text("hello")
    assert file.read_text() == "hello"
    # 测试结束后自动清理

11.2 捕获输出

def test_output(capsys):
    print("hello")
    captured = capsys.readouterr()
    assert captured.out == "hello\n"

11.3 环境变量

pip install pytest-env
[pytest]
env =
    TEST_MODE=staging
    DB_HOST=localhost

11.4 失败重试

pip install pytest-rerunfailures
pytest --reruns 3 --reruns-delay 1

十二、总结

特性unittestpytest
测试编写需继承 TestCase普通函数即可
断言方式self.assertXxx()原生 assert
测试发现遵循命名约定命名约定 + 插件增强
FixturesetUp/tearDown@fixture,更灵活
参数化需手写循环@parametrize 原生支持
插件生态较少非常丰富
学习成本低(标准库)中等

Pytest 凭借简洁的语法、强大的 fixture 机制和丰富的插件生态,已成为 Python 自动化测试的首选框架。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值