pytest框架---参数化(Parametrize)

@pytest.mark.parametrize 是一个"用例复印机 + 填表机"。

python 复制代码
@pytest.mark.parametrize('case_info', [{"user":"admin"}, {"user":"guest"}, {"user":"test"}], ids=['正常登录', '密码错误', '账号不存在'])
def test_login(self,case_info):
...
python 复制代码
# 写了两个名字 'username, password'
@pytest.mark.parametrize('username, password', [('admin', '123'), ('guest', '456')])
def test_login(username, password):  # ⬅️ 这里必须正好有两个形参,且名字完全匹配
    print(username, password)

@pytest.mark.parametrize 是一个"用例复印机 + 填表机"。

你在装饰器里传入一个列表(List),列表里有几条数据,它就把下面那个测试函数复印出几份独立的副本。

每一份副本,都会把列表里的对应数据,自动填入测试函数的参数中。

关键是:这些副本在 Pytest 看来,是完全不同的测试用例(拥有独立的测试结果、独立的执行顺序、独立的失败/通过状态)。

第一个参数:

@pytest.mark.parametrize('password', 它的第一个参数要加引号,pytest.mark.parametrize的第一个参数和test_login中参数名字一样就可以了,随便用什么名字,只要一样就可以

用例名称:

针对你这段具体的代码,

如果写 ids= 参数,用例名称将会是

test_login正常登录

test_login密码错误

test_login账号不存在

如果不写 ids= 参数,Pytest 会自动使用参数值的原始字符串表示(repr) 作为用例名称。

控制台显示的用例名称将会是:

test_login{'user': 'admin'}

test_login{'user': 'guest'}

test_login{'user': 'test'}

'case_info' 和 列表的关系是"遍历赋值"关系

Pytest 会遍历 列表,把列表里的【每一个元素】依次赋值给【名为 case_info 的参数】,每赋值一次,就生成并执行一条全新的测试用例

第二个参数:

@pytest.mark.parametrize 的第二个参数不一定是列表(list),它只需要是一个可迭代对象(Iterable)即可

python 复制代码
@pytest.fixture
def user_token(request):                 # ① 定义了一个 Fixture,但它多了一个 request 参数
    username = request.param              # ② 从 request 对象中取出参数化传入的值
    return get_token(username)            # ③ 调用函数,把 'admin' 变成真正的 token 字符串

@pytest.mark.parametrize('user_token', ['admin', 'guest'], indirect=True)  # ④ 关键!indirect=True
def test_api(user_token):                 # ⑤ 测试函数接收的参数名也叫 user_token
    print(user_token)                     # ⑥ 打印的是 token,而不是 'admin' 

indirect=True:

这是理解这段代码的钥匙。如果没有 indirect=True,Pytest 会直接把 'admin' 和 'guest' 这两个字符串赋值给 test_api 的 user_token 参数,打印出来的就是 admin。

加了 indirect=True 后,传递路径发生了"转向":

普通情况(无 indirect):数据 'admin' → 直接进入 test_api(user_token)。

加上 indirect=True:数据 'admin' → 先进入 @pytest.fixture 定义的 user_token(request) 函数 → 经过加工(生成 token) → 再把加工结果传给 test_api(user_token)。

执行流程时间线:

Pytest 收集阶段:扫描到 @parametrize,发现有一个数据列表 'admin', 'guest',且指定了 indirect=True。

查找 Fixture:Pytest 看到参数名是 user_token,于是去查找名字叫 user_token 的 Fixture(即上面那个被 @pytest.fixture 装饰的函数)。

第 1 轮执行('admin'):

Pytest 把 'admin' 塞进 Fixture 的 request.param 中。

执行 Fixture 函数体内的代码:username = request.param(此时 username 是 'admin')。

调用 get_token('admin'),假设返回 "token_for_admin_123"。

Fixture 把这个 "token_for_admin_123" 返回给 Pytest。

执行测试函数:Pytest 带着 "token_for_admin_123",调用 test_api(user_token),此时 user_token 的值就是那串长长的 token。打印输出 token_for_admin_123。

第 2 轮执行('guest'):重复步骤 3~4,但原始数据换成 'guest',打印出对应的 guest token。

不明白username 是 'admin', 为什么是admin? request.param 还有request. 什么?

为什么 username 是 'admin'? 因为 @pytest.mark.parametrize 的数据列表里第一个元素就是字符串 'admin',Pytest 把它原封不动地塞进了 request.param 里。

request. 还有什么? request 是一个内置的"万能上下文对象",除了 .param,它还有超级多有用的属性,比如 .node、.config、.module 等。

参数化测试:

用一组数据测试多种场景

接口测试中,我们经常需要用多组不同的输入数据去测试同一个接口,比如测试登录接口时,要测正确密码、错误密码、空密码等。如果为每组数据写一个测试函数,代码会非常冗余。pytest的 @pytest.mark.parametrize 装饰器完美解决了这个问题。

python 复制代码
import pytest

# 假设有一个简单的登录校验函数(模拟接口)
def login(username, password):
    if username == "admin" and password == "123456":
        return {"code": 0, "msg": "登录成功"}
    elif not username or not password:
        return {"code": 1001, "msg": "用户名或密码不能为空"}
    else:
        return {"code": 1002, "msg": "用户名或密码错误"}

# 使用参数化装饰器
@pytest.mark.parametrize(
    "username, password, expected_code, expected_msg",  # 参数名,与测试函数参数一一对应
    [
        ("admin", "123456", 0, "登录成功"),          # 用例1:正确账号密码
        ("admin", "wrong", 1002, "用户名或密码错误"), # 用例2:错误密码
        ("", "123456", 1001, "用户名或密码不能为空"), # 用例3:用户名为空
        ("admin", "", 1001, "用户名或密码不能为空"),   # 用例4:密码为空
        (None, "123456", 1001, "用户名或密码不能为空"),# 用例5:用户名为None
    ]
)
def test_login_parametrize(username, password, expected_code, expected_msg):
    """参数化测试登录功能"""
    result = login(username, password)
    assert result["code"] == expected_code
    assert result["msg"] == expected_msg

运行这个测试,pytest会将其展开为5个独立的测试用例并分别执行和报告。在接口自动化中,你可以轻松地将请求参数和预期响应写成这样的列表,实现数据与代码的分离。