Pytest入门系列之pytest的参数化

Pytest入门系列之pytest的参数化

pytest 参数化 允许我们在同一个测试函数中运行多个输入组合,避免重复代码,提高测试覆盖率,特别适用于:

✅单元测试(多个输入输出组合)
API 测试(不同请求参数)
✅数据库测试(不同数据集)
✅前端 UI 测试(不同浏览器)

@pytest.mark.parametrize 基本用法

🔹 示例:测试加法函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pytest

# 被测试函数:简单加法
def add(x, y):
return x + y

# 参数化:测试多个输入组合
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3), # 1 + 2 = 3
(-1, 5, 4), # -1 + 5 = 4
(0, 0, 0), # 0 + 0 = 0
])
def test_add(a, b, expected):
assert add(a, b) == expected # 验证结果是否正确

📌 执行测试

1
pytest -v

📌 测试输出

1
2
3
test_example.py::test_add[1-2-3] PASSED
test_example.py::test_add[-1-5-4] PASSED
test_example.py::test_add[0-0-0] PASSED

📌 解析

@pytest.mark.parametrize(“参数名1, 参数名2, 期望值”, [(参数1, 参数2, 期望值)])

每个测试用例都会运行一次,传入不同参数。

避免重复编写多个 test_add_xxx 测试函数,提高可读性。

企业实战:参数化 API 测试

📌 应用场景

API 需要测试多个请求参数
✅检查 API返回的 HTTP 状态码
✅验证 API 返回的数据结构

🔹 示例:测试 API 响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pytest
import requests

BASE_URL = "https://jsonplaceholder.typicode.com/posts"

# 参数化 API 请求测试
@pytest.mark.parametrize("post_id, expected_status", [
(1, 200), # 测试 ID 1,期望 200
(100, 200), # 测试 ID 100,期望 200
(9999, 404),# 测试 ID 不存在,期望 404
])
def test_api_response(post_id, expected_status):
response = requests.get(f"{BASE_URL}/{post_id}") # 发送请求
assert response.status_code == expected_status # 验证 HTTP 状态码

📌 执行测试

1
pytest -v

📌 测试输出

1
2
3
test_api.py::test_api_response[1-200] PASSED
test_api.py::test_api_response[100-200] PASSED
test_api.py::test_api_response[9999-404] PASSED

📌 解析

不同 post_id 发送 API 请求

检查 response.status_code 是否符合预期

同一函数可测试多个 API 场景,提升测试效率

参数化 UI 自动化测试

📌 应用场景

✅跨浏览器测试
✅不同用户角色测试

🔹 示例: UI 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pytest
from selenium import webdriver

# 参数化:支持不同浏览器
@pytest.mark.parametrize("browser", ["chrome", "firefox"])
def test_open_google(browser):
if browser == "chrome":
driver = webdriver.Chrome() # 启动 Chrome
elif browser == "firefox":
driver = webdriver.Firefox() # 启动 Firefox
else:
pytest.fail("❌ 不支持的浏览器")

driver.get("https://www.google.com") # 打开 Google
assert "Google" in driver.title # 断言页面标题
driver.quit() # 关闭浏览器

📌 执行测试

1
pytest -v

📌 测试输出

1
2
test_ui.py::test_open_google[chrome] PASSED
test_ui.py::test_open_google[firefox] PASSED

📌 解析

test_open_google 里,自动运行 2 次,分别测试 ChromeFirefox

提升 ``UI` 自动化测试的兼容性

参数组合(笛卡尔积)

📌 测试不同浏览器 + 用户角色的组合

🔹 示例:参数组合

1
2
3
4
5
@pytest.mark.parametrize("browser", ["chrome", "firefox"])
@pytest.mark.parametrize("user_role", ["admin", "guest"])
def test_login(browser, user_role):
print(f"🖥️ 浏览器: {browser}, 👤 用户角色: {user_role}")

📌 执行测试

1
pytest -v

📌 生成的组合

1
2
3
4
5
test_login[chrome-admin] PASSED
test_login[chrome-guest] PASSED
test_login[firefox-admin] PASSED
test_login[firefox-guest] PASSED

📌 4 组测试组合,自动生成(笛卡尔积)

使用 pytest.param 进行标记

📌 pytest.param 允许:

✅单独设置 pytest.mark.xfail(预期失败)
✅单独为测试添加 id 方便区分

🔹 示例:部分测试标记 xfail

1
2
3
4
5
6
7
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3), # 正常通过
pytest.param(2, 2, 5, marks=pytest.mark.xfail(reason="已知问题")), # 预期失败
])
def test_math(a, b, expected):
assert a + b == expected

📌 执行测试

1
pytest -v

📌 输出

1
2
test_math[1-2-3] PASSED
test_math[2-2-5] XFAIL # 预期失败,不影响整体测试

📌 适用于

✅标记已知缺陷
✅跳过特定测试

从 Excel/Yaml/Json/数据库 动态加载参数

从 Excel 读取参数化数据

📌 场景:

Excel 文件 常用于管理 测试数据,比如:
✅ 用户登录测试(账号、密码)
✅ 接口测试(API 参数)
✅ 前端 UI 测试(不同输入数据)

📌 为什么使用 pandas 处理 Excel?

更快:比 openpyxl 解析速度快

功能更强:可以处理 大规模数据

数据过滤、转换更方便

🔹 安装 pandas

1
pip install pandas

📌 pandas 用于解析 Excel

🔹 创建 Excel 文件(test_data.xlsx)

📌 Excel 示例数据

用户名 密码 预期结果
admin admin123 success
user1 pass123 success
guest wrongpwd failure

🔹 代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pytest
import pandas as pd

# 读取 Excel 文件
def load_excel_data(file, sheet_name):
df = pd.read_excel(file, sheet_name=sheet_name) # 读取 Excel
return df.values.tolist() # 转换为列表

# 参数化测试,读取 Excel 数据
@pytest.mark.parametrize("username, password, expected", load_excel_data("test_data.xlsx", "Sheet1"))
def test_login_excel(username, password, expected):
print(f"🔐 测试登录 - 用户名: {username}, 密码: {password}")
assert (username == "admin" and password == "admin123") == (expected == "success")

🔹 案例: Excel 复杂数据处理

📌 处理数据格式

✅去掉 NaN
✅过滤掉某些列
✅修改数据格式

🔹 代码示例:

1
2
3
4
5
6
def load_cleaned_excel_data(file, sheet_name):
df = pd.read_excel(file, sheet_name=sheet_name)
df = df.dropna() # 删除 NaN 数据
df["username"] = df["username"].astype(str) # 确保所有用户名为字符串
return df[["username", "password", "expected"]].values.tolist() # 只保留三列

📌 使用 pytest.mark.parametrize

1
2
3
4
@pytest.mark.parametrize("username, password, expected", load_cleaned_excel_data("test_data.xlsx", "Sheet1"))
def test_login_excel_clean(username, password, expected):
print(f"🔐 测试登录 - 用户名: {username}, 密码: {password}")
assert (username == "admin" and password == "admin123") == (expected == "success")

从 YAML 读取参数化数据

📌 场景:

YAML 适用于 配置类测试(如环境变量、API 测试)

🔹 安装依赖

1
pip install pyyaml

🔹 创建 YAML 文件 (config.yaml)

1
2
3
4
5
6
7
8
9
10
11
users:
- username: "admin"
password: "admin123"
expected: "success"
- username: "user1"
password: "pass123"
expected: "success"
- username: "guest"
password: "wrongpwd"
expected: "failure"

🔹 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pytest
import yaml

# 读取 YAML 数据
def load_yaml_data(file):
with open(file, "r", encoding="utf-8") as f:
data = yaml.safe_load(f)
return [(user["username"], user["password"], user["expected"]) for user in data["users"]]

# 参数化 YAML 数据
@pytest.mark.parametrize("username, password, expected", load_yaml_data("config.yaml"))
def test_login_yaml(username, password, expected):
print(f"🔐 YAML 测试登录 - 用户名: {username}, 密码: {password}")
assert (username == "admin" and password == "admin123") == (expected == "success")

📌 适用于

✅ 测试环境变量、配置类参数
API 测试数据维护

从 JSON 读取参数化数据

📌 场景:

JSON 适用于 API 测试
✅接口入参存储在 JSON 文件中

🔹 创建 JSON 文件 (test_data.json)

1
2
3
4
5
6
[
{ "username": "admin", "password": "admin123", "expected": "success" },
{ "username": "user1", "password": "pass123", "expected": "success" },
{ "username": "guest", "password": "wrongpwd", "expected": "failure" }
]

🔹 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pytest
import json

# 读取 JSON 数据
def load_json_data(file):
with open(file, "r", encoding="utf-8") as f:
data = json.load(f)
return [(user["username"], user["password"], user["expected"]) for user in data]

# 参数化 JSON 数据
@pytest.mark.parametrize("username, password, expected", load_json_data("test_data.json"))
def test_login_json(username, password, expected):
print(f"🔐 JSON 测试登录 - 用户名: {username}, 密码: {password}")
assert (username == "admin" and password == "admin123") == (expected == "success")

📌 适用于
REST API 测试
✅ 数据驱动测试(DDT)

从 MySQL 数据库读取参数化数据

📌 场景:

✅直接从 MySQL 获取测试数据(如用户信息、订单信息)
✅适用于 数据驱动测试(DDT)

🔹 安装 pymysql

1
pip install pymysql

🔹 创建测试数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE DATABASE test_db;
USE test_db;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(50),
expected VARCHAR(10)
);
INSERT INTO users (username, password, expected) VALUES
("admin", "admin123", "success"),
("user1", "pass123", "success"),
("guest", "wrongpwd", "failure");

🔹 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pytest
import pymysql

# 连接 MySQL,查询数据
def load_mysql_data():
conn = pymysql.connect(host="localhost", user="root", password="root", database="test_db")
cursor = conn.cursor()
cursor.execute("SELECT username, password, expected FROM users")
data = cursor.fetchall()
conn.close()
return data

# 参数化数据库数据
@pytest.mark.parametrize("username, password, expected", load_mysql_data())
def test_login_mysql(username, password, expected):
print(f"🔐 MySQL 测试登录 - 用户名: {username}, 密码: {password}")
assert (username == "admin" and password == "admin123") == (expected == "success")

📌 适用于
✅ 数据库测试(如订单、用户数据)
✅ 测试真实业务数据

总结

数据源 适用场景 预期结代码示例
Excel(pandas) 手动维护的测试数据(如账号/密码) load_excel_data("test_data.xlsx")
YAML(pyyaml) 配置文件、环境变量测试 load_yaml_data("config.yaml")
JSON(json) API 参数测试 load_json_data("test_data.json")
MySQL(pymysql) 数据库数据驱动测试 load_mysql_data()

以上几种的参数化都会极大的提高我们的工作效率,至于选择什么作为数据源基本都差不多,当然还是要结合你的实际业务选择!


Pytest入门系列之pytest的参数化
https://dreamshao.github.io/2025/03/25/pytest入门系列9/
作者
Yun Shao
发布于
2025年3月25日
许可协议