pytest 是 Python 生态中最流行的单元测试框架之一,用于编写、组织和运行自动化测试代码。广泛应用于功能测试、接口测试、单元测试等场景。

pytest的用例规则是怎么样的?

默认发现规则(即零配置即可生效)文件命名规则

  • 必须满足前缀或者后缀是test,非匹配文件会被忽略
    • test_*.py(如 test_login.py
    • *_test.py(如 login_test.py
  • 测试函数:必须以 test_ 开头
  • 测试类:类名以 Test 开头,且不能有 __init__ 方法
  • 方法名以 test_ 开头

⚙️ 自定义规则(通过 pytest.ini 修改)

1
2
3
4
5
# pytest.ini 示例
[pytest]
python_files = check_*.py # 修改文件匹配模式
python_classes = *Test # 修改类匹配模式
python_functions = verify_* # 修改函数匹配模式

📂 目录结构规范

1
2
3
4
5
6
7
8
9
project/
├── tests/ # 测试目录(通常命名 tests/ 或 test/)
│ ├── __init__.py # 使目录成为Python包(可选)
│ ├── test_web/ # 模块化分组
│ │ ├── test_login.py
│ │ └── test_api.py
│ └── test_db.py
├── src/ # 主代码目录
└── pytest.ini # 配置文件

🔍 基础执行控制

参数 作用 示例
-v / --verbose 显示详细输出(用例名称+结果) pytest -v
-q / --quiet 简化输出(仅显示结果摘要) pytest -q
-x 遇到第一个失败后立即停止 pytest -x
--maxfail=n 允许最多 n 次失败后停止 pytest --maxfail=2
-k "表达式" 模糊匹配用例名/类名 pytest -k "login"
-m "标记" 运行指定标记的用例 pytest -m "slow"

标记(Mark)

标记的核心作用是灵活控制测试的执行逻辑,比如跳过某些测试、指定测试优先级、分组运行等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 定义标记
import pytest

@pytest.mark.smoke # 标记为「冒烟测试」
def test_login():
assert True

@pytest.mark.performance # 标记为「性能测试」
def test_load():
assert 1 + 1 == 2

# 通过命令行运行指定标记的测试
pytest -m "smoke" # 只运行标记为 smoke 的测试
pytest -m "not performance" # 排除 performance 标记的测试

自定义标记

在 pytest.ini 中声明标记

1
2
3
4
5
6
7
8
9
10
[pytest]
markers =
smoke: 冒烟测试用例
performance: 性能测试
slow: 慢速测试(需额外时间)

# 使用
@pytest.mark.slow
def test_big_data_processing():
assert process_data() == expected

内置标记

pytest 提供内置标记,它们无需额外声明即可直接使用,专门用于处理常见的测试场景。内置标记无需注册,直接使用。同时优先级也会更高,会覆盖自定义标记的同名行为。

特性 内置标记 自定义标记
是否需要声明 ❌ 直接使用 ✅ 需在 pytest.ini 声明
是否可修改行为 ❌ 固定逻辑 ✅ 可自由定义
典型用途 跳过/参数化/预期失败等标准场景 业务分组(如smoke/regression)

📌 pytest 主要内置标记

1. 跳过测试相关

标记 作用
@pytest.mark.skip 无条件跳过测试用例
@pytest.mark.skipif 满足条件时跳过(需指定条件表达式)
@pytest.mark.xfail 标记预期失败的测试(失败时输出XFAIL,意外通过则XPASS)
@pytest.mark.xfail(strict=True) 严格模式:如果意外通过则报错(用于确保修复后移除标记)
@pytest.mark.parametrize 多组参数驱动同一测试

内置标记使用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 场景1:跨平台测试控制
import sys

@pytest.mark.skipif(
sys.platform != "linux",
reason="仅Linux环境测试"
)
def test_linux_syscall():
assert os.system("ls") == 0

# 场景2:版本依赖跳过
@pytest.mark.skipif(
pandas.__version__ < "2.0.0",
reason="需要pandas 2.0+"
)

夹具(Fixture)

用于为测试用例提供预置条件清理操作的核心机制,相当于测试的「脚手架」。通过 @pytest.fixture 装饰器定义,减少重复代码并提升测试的可维护性。

🎯 夹具的核心作用

  1. 初始化资源:如数据库连接、测试数据
  2. 共享复用代码:避免重复编写 setup/teardown
  3. 依赖注入:自动将夹具传递给测试函数
  4. 保证清理:无论测试成功/失败都会执行清理
1
2
3
4
5
6
7
8
9
10
11
12
import pytest
# 使用 @pytest.fixture 装饰器定义夹具
@pytest.fixture
def my_fixture():
# 设置代码
data = {"key": "value"}
yield data # 这是测试将使用的值
# 清理代码(如果有)

# 使用夹具,在测试函数中将夹具作为参数传入
def test_example(my_fixture):
assert my_fixture["key"] == "value"

夹具的作用域控制

1
2
3
4
5
6
7
8
9
@pytest.fixture(scope="function")  # 默认,每个测试函数执行一次
@pytest.fixture(scope="class") # 每个测试类执行一次
@pytest.fixture(scope="module") # 每个模块执行一次
@pytest.fixture(scope="session") # 整个测试会话执行一次

# utouse=True 让夹具自动执行,无需显式请求
@pytest.fixture(autouse=True)
def auto_fixture():
print("\n这个夹具会自动在每个测试中运行")

夹具可以依赖其他夹具

1
2
3
4
5
6
7
8
9
10
11
@pytest.fixture
def db_connection():
conn = create_db_connection()
yield conn
conn.close()

@pytest.fixture
def user_table(db_connection): # 依赖db_connection夹具
db_connection.create_table("users")
yield
db_connection.drop_table("users")

夹具工厂

当需要参数化夹具时,可以使用夹具工厂模式:

1
2
3
4
5
6
7
8
9
@pytest.fixture
def make_user():
def _make_user(name, age):
return {"name": name, "age": age}
return _make_user

def test_user_factory(make_user):
user = make_user("Alice", 25)
assert user["name"] == "Alice"

插件

插件分为两类,一类是内置的,一类是第三方。
使用的方式也很简单,-p xxx是启用,-p no xxx是禁用。
插件一般使用方法都是通过上面的方法:参数,配置文件,夹具,mark