pytest 参数化测试:同一用例跑多组数据,数据驱动就这么简单
Introduction
测试登录功能,你需要验证:
- 用户名正确 + 密码正确 → 登录成功
- 用户名正确 + 密码错误 → 登录失败
- 用户名为空 → 验证失败
- 用户不存在 → 登录失败
没有参数化?你需要写 4 个几乎一样的测试函数。用 @pytest.mark.parametrize,一行代码搞定。
---
基础语法
1. 单参数
import pytest
@pytest.mark.parametrize("username", ["admin", "test", "guest"])
def test_username_format(username):
assert len(username) >= 3
assert username.isalnum()
运行结果:3个测试用例分别执行。
2. 多参数组合
@pytest.mark.parametrize("username,password,expected", [
("admin", "123456", True),
("admin", "wrong", False),
("", "123456", False),
("nobody", "anypass", False),
])
def test_login(username, password, expected):
result = login(username, password)
assert result == expected
pytest 自动生成 4 个测试组合,覆盖所有场景。
3. 与 Fixtures 配合
@pytest.fixture
def api_client():
return APIClient(base_url="https://api.example.com")
@pytest.mark.parametrize("endpoint,status_code", [
("/users", 200),
("/orders", 200),
("/products", 200),
])
def test_api_endpoints(api_client, endpoint, status_code):
response = api_client.get(endpoint)
assert response.status_code == status_code
4. 反向参数(ids)
给每个测试用例起一个可读的名字:
@pytest.mark.parametrize("username,password,expected", [
("admin", "123456", True),
("admin", "wrong", False),
], ids=["valid_creds", "invalid_password"])
def test_login(username, password, expected):
...
运行输出会显示 test_login[valid_creds] 而不是 test_login[0]。
5. 间接参数(indirect)
把参数传给 fixture 而不是直接传给测试函数:
@pytest.fixture
def db_connection(db_name):
return Database(db_name)
@pytest.mark.parametrize("db_name", ["test_db", "prod_db"], indirect=["db_name"])
def test_database_query(db_connection, db_name):
result = db_connection.query("SELECT 1")
assert result is not None
---
运行效果
$ pytest test_parametrize.py -v
test_parametrize.py::test_login[valid_creds] PASSED [ 25%]
test_parametrize.py::test_login[invalid_password] PASSED [ 25%]
test_parametrize.py::test_username_format[admin] PASSED [ 50%]
test_parametrize.py::test_username_format[test] PASSED [ 75%]
test_parametrize.py::test_username_format[guest] PASSED [100%]
---
常见问题
Q1: 只想跑其中某组参数?
pytest -k "valid_creds"
Q2: 全部参数都失败才报错?
不是,pytest 独立运行每组参数,一组失败不影响其他组。Q3: 参数太多写不下?
可以用外部文件(CSV/JSON)存储测试数据,然后用pytest_generate_tests 动态加载:
import pytest
import json
def pytest_generate_tests(metafunc):
if "test_data" in metafunc.fixturenames:
with open("test_data.json") as f:
data = json.load(f)
metafunc.parametrize("test_data", data)
---
延伸阅读
- [[pytest Fixtures 详解]] — 测试夹具的创建与作用域
- [[pytest 插件生态]] — 覆盖率报告、并行执行、HTML报告
- [[Selenium + pytest 集成]] — 数据驱动的浏览器自动化测试
---
有问题欢迎在评论区交流!