在之前的文章中,我们已经领略了 Pytest 的简洁语法、强大的断言以及灵活的 Fixture 功能。今天,我们将聚焦 Pytest 的另一个核心特性——参数化 (Parametrization) 测试。参数化测试允许我们使用不同的输入数据来运行同一个测试函数,从而有效地提高测试覆盖率,减少重复的代码编写,并使我们的测试更加全面和健壮。
回顾:为测试准备舞台
正如我们在第三篇中学习到的,Fixture 可以为我们的测试函数提供必要的预置条件和资源。而参数化测试则更进一步,它允许我们针对同一测试逻辑,使用多组不同的输入数据和预期结果进行验证,就像为同一个舞台准备了多场不同演员的演出。
什么是参数化测试?数据驱动的强大力量
参数化测试的核心思想是将测试函数的输入数据和预期结果与测试逻辑本身分离。通过为测试函数提供多组不同的参数,Pytest 会自动使用每一组参数组合多次运行该测试函数。这对于验证同一功能在不同场景下的行为非常有用,例如:
- 验证不同边界条件下的数值计算。
- 使用不同的用户凭据测试登录功能。
- 测试不同类型的输入数据对函数的影响。
使用 @pytest.mark.parametrize
装饰器
Pytest 提供了 @pytest.mark.parametrize
装饰器来实现参数化测试。你需要将这个装饰器应用到你的测试函数上,并指定参数的名称和测试数据的列表。
# 使用参数化parametrize
import pytest@pytest.mark.parametrize("value,result_value", [(1, 5),(2, 10),(3, 15),(4, 22),(-5, -25)
])
def test_quare_function(value, result_value):result = value * 5assert result == result_value输出如下;可以清楚的看到;具体是哪一对参数有问题
============================= test session starts =============================
collecting ... collected 5 items05_Parametrization_test.py::test_quare_function[1-5]
05_Parametrization_test.py::test_quare_function[2-10]
05_Parametrization_test.py::test_quare_function[3-15]
05_Parametrization_test.py::test_quare_function[4-22]
05_Parametrization_test.py::test_quare_function[-5--25] ========================= 1 failed, 4 passed in 0.09s =========================
PASSED [ 20%]PASSED [ 40%]PASSED [ 60%]FAILED [ 80%]
05_Parametrization_test.py:10 (test_quare_function[4-22])
20 != 22Expected :22
Actual :20
<Click to see difference>value = 4, result_value = 22@pytest.mark.parametrize("value,result_value", [(1, 5),(2, 10),(3, 15),(4, 22),(-5, -25)])def test_quare_function(value, result_value):result = value * 5
> assert result == result_value
E assert 20 == 2205_Parametrization_test.py:20: AssertionError
PASSED [100%]
Process finished with exit code 1@pytest.mark.parametrize("username,password,result", [("test", "test_pwd", True),("admin", "admin_pwd", False),("admin", "123123", True),("test_admin", "test_admin_pwd", False)
])
def test_login(username, password, result):# 假设这是一个Login登陆判断def login(u, p):if u == "admin" and p == "123123":return Truereturn Falseassert login(username, password) == result输出内容如下
============================= test session starts =============================
collecting ... collected 4 items05_Parametrization_test.py::test_login[test-test_pwd-True]
05_Parametrization_test.py::test_login[admin-admin_pwd-False]
05_Parametrization_test.py::test_login[admin-123123-True]
05_Parametrization_test.py::test_login[test_admin-test_admin_pwd-False] ========================= 1 failed, 3 passed in 0.09s =========================
FAILED [ 25%]
05_Parametrization_test.py:22 (test_login[test-test_pwd-True])
False != TrueExpected :True
Actual :False
<Click to see difference>username = 'test', password = 'test_pwd', result = True@pytest.mark.parametrize("username,password,result", [("test", "test_pwd", True),("admin", "admin_pwd", False),("admin", "123123", True),("test_admin", "test_admin_pwd", False)])def test_login(username, password, result):# 假设这是一个Login登陆判断def login(u, p):if u == "admin" and p == "123123":return Truereturn False> assert login(username, password) == result
E AssertionError: assert False == True
E + where False = <function test_login.<locals>.login at 0x0000018CF5FB2660>('test', 'test_pwd')05_Parametrization_test.py:36: AssertionError
PASSED [ 50%]PASSED [ 75%]PASSED [100%]
Process finished with exit code 1
在上面的例子中:
test_square_function
使用了两组参数:value
和result_value
。@pytest.mark.parametrize
接收两个参数:第一个是包含参数名的字符串 (多个参数名用逗号分隔),第二个是包含测试数据的列表。列表中的每个元素都是一个元组,元组中的每个值对应一个参数名。Pytest 将会使用(1, 5)
、(2, 10)
等不同的参数组合多次运行test_square_function
。test_login
使用了四组参数:username
,password
, 和result
,用于测试登录功能在不同凭据下的行为。
参数化 Fixture:为 Fixture 提供多组数据
Fixture 也可以进行参数化。这在你需要为不同的测试场景提供不同的测试环境或资源时非常有用。你可以在 @pytest.fixture
装饰器中使用 params
参数来指定多组数据。
# fixture的参数化
@pytest.fixture(params=["chrome", "firefox", "webkit"])
def browser_name(request):return request.paramdef test_browser(browser_name):print(f"{browser_name}开始表演")# isinstance判断类型是否为strassert isinstance(browser_name, str)输出内容如下
============================= test session starts =============================
collecting ... collected 3 items05_Parametrization_test.py::test_browser[chrome] PASSED [ 33%]chrome开始表演05_Parametrization_test.py::test_browser[firefox] PASSED [ 66%]firefox开始表演05_Parametrization_test.py::test_browser[webkit] PASSED [100%]webkit开始表演============================== 3 passed in 0.01s ==============================
在这个例子中,browser_name
Fixture 使用 params
指定了三个浏览器名称。当测试函数 test_browser_rendering
依赖这个 Fixture 时,Pytest 会使用 “chrome”、“firefox” 和 “webkit” 分别运行该测试函数。
参数化的命名和显示:让测试报告更清晰
默认情况下,参数化测试用例在报告中会以数字索引显示,例如 test_square_function[0]
、test_square_function[1]
等。为了让测试报告更具可读性,你可以使用 @pytest.mark.parametrize
的 ids
参数来为每个参数组合指定自定义的名称。
@pytest.mark.parametrize("value,result_value", [(1, 5),(2, 10),(-5, -25)
], ids=["first", "second", "fifths"], )
def test_quare_function(value, result_value):result = value * 5assert result == result_value输出内容如下,可以看到方法后面会跟着自定义的参数名
============================= test session starts =============================
collecting ... collected 3 items05_Parametrization_test.py::test_quare_function[first] PASSED [ 33%]
05_Parametrization_test.py::test_quare_function[second] PASSED [ 66%]
05_Parametrization_test.py::test_quare_function[fifths] PASSED [100%]============================== 3 passed in 0.01s ==============================@pytest.mark.parametrize("username,password,result", [("admin", "admin_pwd", False),("admin", "123123", True),("test_admin", "test_admin_pwd", False)
], ids=["错误1", "正确的", "错误2"])
def test_login(username, password, result):# 假设这是一个Login登陆判断def login(u, p):if u == "admin" and p == "123123":return Truereturn Falseassert login(username, password) == result输出如下
============================= test session starts =============================
collecting ... collected 3 items05_Parametrization_test.py::test_login[\u9519\u8bef1] PASSED [ 33%]
05_Parametrization_test.py::test_login[\u6b63\u786e\u7684] PASSED [ 66%]
05_Parametrization_test.py::test_login[\u9519\u8bef2] PASSED [100%]============================== 3 passed in 0.01s ==============================
- 这里可以看到,当
ids
参数为中文的时候,没有转义出来;这个时候,只需要在pytest.ini
文件里,新增一个参数即可 disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
============================= test session starts =============================
collecting ... collected 3 items05_Parametrization_test.py::test_login[错误1] PASSED [ 33%]
05_Parametrization_test.py::test_login[正确的] PASSED [ 66%]
05_Parametrization_test.py::test_login[错误2] PASSED [100%]============================== 3 passed in 0.01s ==============================
ids
参数可以是一个字符串列表,其长度必须与测试数据的列表长度一致。不够、超出都会报错ids
也可以是一个函数,该函数接收当前的参数组合 (通常是一个元组) 作为输入,并返回一个字符串作为该参数组合的 ID。
#接受parametrize传入的参数
def ids_function(value):return f"{value}..."
# ids也可以接受一个函数
@pytest.mark.parametrize("value,result_value", [(1, 5),(2, 10),(-5, -25)
], ids=ids_function)
def test_quare_function(value, result_value):result = value * 5assert result == result_value输出内容如下,可以看到他会取每一个元组的值,并且以-符号连接起来
============================= test session starts =============================
collecting ... collected 3 items05_Parametrization_test.py::test_quare_function[1...-5...] PASSED [ 33%]
05_Parametrization_test.py::test_quare_function[2...-10...] PASSED [ 66%]
05_Parametrization_test.py::test_quare_function[-5...--25...] PASSED [100%]============================== 3 passed in 0.01s ==============================
在测试函数和 Fixture 中使用参数化数据
一旦你在测试函数或 Fixture 上使用了 @pytest.mark.parametrize
,Pytest 会自动将参数值传递给对应的参数名。你可以在测试逻辑中直接使用这些参数。
总结
Pytest 的参数化测试是一个非常强大且实用的特性,它能够帮助你:
- 提高测试覆盖率: 通过使用多组不同的输入数据验证同一功能。
- 减少代码冗余: 避免为不同的测试数据编写重复的测试函数。
- 提高测试的可读性: 将测试逻辑与测试数据分离。
- 生成更清晰的测试报告: 通过自定义的
ids
更好地理解每个测试用例的场景。
掌握参数化测试,将使你的 Pytest 自动化测试技能更上一层楼,能够编写出更全面、更健壮的测试用例。在接下来的文章中,我们将探索 Pytest 的钩子函数 (Hook Functions),学习如何在 Pytest 的测试流程中插入自定义逻辑,进一步定制你的测试行为。请继续关注!