在自动化测试(尤其是 GUI、接口自动化)中,我们经常遇到同一个业务逻辑,需要用多组不同的输入数据来验证的场景。比如登录功能:正确账号 + 正确密码、错误账号 + 正确密码、正确账号 + 错误密码...... 如果每组数据都写一个测试用例,代码会大量冗余,维护成本极高。
pytest 内置的参数化功能 ,就是解决这个问题的 "神器"。它能让一个测试函数,自动循环执行多组数据,代码极简、扩展性极强,是自动化测试框架必备技能。
一、什么是参数化?为什么要用它?
1. 概念
参数化设计是自动化测试中的重要组成部分,它通过定义测试参数和预期结果,让同一个测试用例自动运行多组不同数据,实现 "一套用例,批量测试" 的效果,让测试过程更灵活、更可控。
2. 核心优势
- 代码复用:不用重复写相似的测试函数,一组逻辑跑所有数据;
- 易维护:新增 / 删除测试数据,只改参数列表,不用动测试逻辑;
- 覆盖率高:轻松覆盖正常、异常、边界等所有测试场景;
- 报告清晰:pytest 会自动给每组数据生成独立的测试结果。
二、pytest 参数化核心:@pytest.mark.parametrize
pytest 通过内置装饰器 @pytest.mark.parametrize 实现参数化,是最常用、最灵活的方式。
基本语法
python
@pytest.mark.parametrize("参数名1,参数名2", [
(数据1_1, 预期1),
(数据2_1, 预期2),
(数据3_1, 预期3),
])
def test_func(参数名1, 参数名2):
# 测试逻辑
assert ...
三、3种常用参数化用法(含你的示例详解)
示例 1:测试函数上使用参数化(最常用)
直接给单个测试函数添加参数,适合独立的测试场景。
python
import pytest
# 定义输入参数 + 预期结果
@pytest.mark.parametrize("test_input, expected", [
("3+5", 8), # 用例1
("2+4", 6), # 用例2
("6*9", 54), # 用例3
])
def test_eval(test_input, expected):
# 执行测试逻辑
assert eval(test_input) == expected
执行效果 : 测试函数会自动运行 3 次,每次传入一组数据,分别断言。 任意一组数据失败,不影响其他组执行,报告中会单独标记失败。
示例 2:测试类上使用参数化
给整个类加参数化,类中所有测试方法都会自动使用这些数据。
python
import pytest
# 给类统一传参
@pytest.mark.parametrize("n, expected", [(1, 2), (3, 4)])
class TestClass:
# 两个方法都会自动接收 n 和 expected
def test_simple_case(self, n, expected):
assert n + 1 == expected
def test_weird_simple_case(self, n, expected):
assert (n * 1) + 1 == expected
适用场景: 类中所有测试方法都需要使用相同的多组数据时使用。
示例 3:模块级参数化(整个文件生效)
给整个测试模块 设置参数,文件中所有测试类、测试函数都会自动使用这些参数。
方式:定义全局变量 pytestmark(名字不能修改)
python
import pytest
# 全局参数化:整个文件生效
pytestmark = pytest.mark.parametrize("n, expected", [(1, 2), (3, 4)])
class TestClass01:
def test_simple_case(self, n, expected):
assert n + 1 == expected
class TestClass02:
def test_weird_simple_case(self, n, expected):
assert (n * 1) + 1 == expected
适用场景: 整个测试文件的用例都依赖同一组参数。
四、高级用法:自定义外部数据源(动态传参)
除了直接写参数列表,还可以从函数、Excel、YAML、JSON 读取测试数据,实现数据与代码分离。
示例 3:函数作为参数数据源
python
# 自定义数据源函数
def data_provider():
return [("admin", "123456"), ("test", "123456"), ("guest", "123456")]
# 直接引用函数
@pytest.mark.parametrize("username, password", data_provider())
def test_login(username, password):
print(f"登录:{username}")
assert username != ""
优势: 数据源可以独立维护,支持从文件 / 数据库读取,适合企业级自动化项目。
五、GUI 自动化实战:登录参数化(最经典场景)
可直接套用的实战示例:
python
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
# 测试数据:用户名、密码、预期结果
@pytest.mark.parametrize("username, pwd, expect_text", [
("correct_user", "correct_pwd", "登录成功"),
("wrong_user", "correct_pwd", "用户名或密码错误"),
("correct_user", "wrong_pwd", "用户名或密码错误"),
("", "", "用户名不能为空"),
])
def test_gui_login(username, pwd, expect_text):
driver = webdriver.Chrome()
driver.get("http://demo.com/login")
# GUI 操作
driver.find_element(By.ID, "username").send_keys(username)
driver.find_element(By.ID, "password").send_keys(pwd)
driver.find_element(By.ID, "loginBtn").click()
# 获取提示文本并断言
tip_text = driver.find_element(By.ID, "tip").text
assert expect_text in tip_text
driver.quit()
效果 : 一个用例,自动测 4 组登录场景,完美覆盖正常、异常、空值。
六、参数化高级小技巧(提升可读性)
1. 给每组数据加用例名称(ids 参数)
ids 是 @pytest.mark.parametrize 的可选参数,作用是给每一组测试用例起一个自定义的、易读的名字,方便你在运行测试、查看报告时快速识别每个用例是测什么的。
python
@pytest.mark.parametrize("username, pwd, expect", [
("admin", "123", "成功"),
("test", "xxx", "失败"),
], ids=["正确账号登录", "错误密码登录"])
def test_login(...):
...
不加 ids 时 测试用例名会自动生成,可读性很差:
bash
test_login[admin-123-成功]
test_login[test-xxx-失败]
加了 ids 后 用例名变成你定义的中文 / 英文描述,一目了然:
test_login[正确账号登录]
test_login[错误密码登录]
2. 多个参数化装饰器叠加(笛卡尔积)
生成所有组合,适合全面覆盖:
python
@pytest.mark.parametrize("a", [1,2])
@pytest.mark.parametrize("b", [3,4])
def test_add(a, b):
# 会执行 (1,3) (1,4) (2,3) (2,4)
assert a + b > 0
七、总结
pytest 参数化是自动化测试最核心、最实用的功能之一,它解决了 "多组数据重复写用例" 的问题。
你必须掌握的 3 种用法:
- 函数参数化:单个用例使用多组数据;
- 类参数化:类中所有用例共享数据;
- 模块参数化:整个文件所有用例统一传参。
额外加分技能:
- 自定义数据源函数
- GUI 自动化登录 / 表单批量测试
学会参数化,你的自动化用例开发效率至少提升 3~5 倍!