参数化

章节目录:

    • 一、参数化概念
    • 二、pytest 中的参数化
      • 2.1 通过装饰器参数化
      • 2.2 通过读取文件参数化
      • 2.3 对测试类进行参数化
      • 2.4 结合 fixture 参数化
      • 2.5 将参数格式化输出
    • 三、补充说明
    • 四、结束语

一、参数化概念

参数化是一种测试技术,它允许在测试过程中使用不同的输入参数运行相同的测试逻辑

  • 参数化的作用主要体现在以下几个方面:

    • 减少重复代码:通过参数化,可以将测试逻辑与输入参数分离,避免编写多个重复的测试方法或函数。相同的测试逻辑可以在不同的参数集上自动运行,从而减少了代码冗余。
    • 提高测试覆盖范围:使用参数化技术,可以轻松地定义多个输入参数组合,使测试能够覆盖更多的场景和边界条件。通过传递不同的参数值,可以测试不同的输入情况,并验证系统在各种情况下的行为是否符合预期。
    • 增加可扩展性:参数化测试使得测试用例的维护和扩展更加容易。当需要增加新的测试场景或输入参数时,只需要在参数化的测试方法或函数中添加相应的参数组合,而不需要修改现有的测试逻辑。
    • 提供清晰的测试报告:通过参数化测试,每个参数组合都会被视为一个独立的测试用例,并在测试报告中单独显示。这样可以更好地跟踪和分析每个测试用例的结果,找出导致失败或异常的具体输入参数组合。
  • 参数化是测试框架中的一个重要特性,常用于单元测试、集成测试和功能测试等各个级别的测试。它可以提高测试的效率、可读性和维护性,帮助开发人员在不同的输入参数组合下更全面地验证系统的行为。

二、pytest 中的参数化

2.1 通过装饰器参数化

最常用的是通过装饰器 @pytest.mark.parametrize() 方式进行参数化。

  • 代码示例
import pytest


def add(a, b):
    return a + b


@pytest.mark.parametrize("a,b", [[1, 2], [3, 4], [5, 6]])
# 第一个参数是以字符串的形式标识用例函数的参数。 -- "a,b"
# 第二个参数以列表或元组的形式传递测试数据。 -- [[1, 2], [3, 4], [5, 6]]
def test_case(a, b):
    # 断言 a,b 之和是否小于等于 10。
    assert add(a, b) <= 10
    # test-demo.py::test_case[1-2] PASSED [ 33%]
    # test-demo.py::test_case[3-4] PASSED [ 66%]
    # test-demo.py::test_case[5-6] FAILED [100%]

  • 注意
    • 多个参数之间要用逗号分隔。
    • 参数名称和个数要一一对应

2.2 通过读取文件参数化

这里以读取 yaml 文件为例,同样也可以将测试数据通过 json 文件进行管理。

  • 项目结构
F:\PROJECTS\TEST-DEMO
│
├─cases
│      test_login.py
│      __init__.py
│
├─data
│      login.yaml
│      __init__.py
│
├─utils
│      file_utils.py
│      __init__.py
└─
  • login.yaml
- case_name: "账号密码正确"
  user_name: "root"
  password: 666666

- case_name: "用户名为空"
  user_name:
  password: 666666

- case_name: "密码为空"
  user_name: "root"
  password:

  • file_utils.py
import yaml


def read_yaml(yaml_file_path):
    """
    读取 YAML 文件并返回解析后的数据。
    :param yaml_file_path: YAML 文件的路径
    :return: 解析后的 YAML 数据
    :raises: IOError, yaml.YAMLError
    """
    try:
        with open(yaml_file_path, "r", encoding="utf-8") as f:
            value = yaml.safe_load(stream=f)
    except IOError:
        # 处理文件读取错误。
        raise
    except yaml.YAMLError:
        # 处理 YAML 解析错误。
        raise

    return value


if __name__ == '__main__':
    print(read_yaml(r'../data/login.yaml'))

  • test_login.py
import pytest

from utils.file_utils import read_yaml


@pytest.mark.parametrize("case", read_yaml(r"../data/login.yaml"))
def test_login(case):
    user_name = case["user_name"]
    password = case["password"]
    assert user_name == "root" and password == 666666
    # PASSED [ 33%]
    # {'case_name': '用户名为空', 'password': 666666, 'user_name': None} FAILED [ 66%]
    # {'case_name': '密码为空', 'password': None, 'user_name': 'root'} FAILED [100%]

2.3 对测试类进行参数化

测试类的参数化,其实际上也是对类中的测试方法进行参数化。类中的测试方法的参数必须与 @pytest.mark.parametrize() 中的标识的参数个数一致

  • 代码示例
import pytest


def add(a, b):
    return a + b


@pytest.mark.parametrize("a,b,c", [[1, 2, 3], [1, 3, 4], [2, 5, 7]])
class TestParams:

    def test_par1(self, a, b, c):
        # 断言 a + b 是否等于 c。
        assert add(a, b) == c

    def test_par2(self, a, b, c):
        assert add(a, b) == c

    # def test_par3(self, a, b):
    # 	  参数个数不一致,报错。
    #     assert add(a, b) > 0
    # In test_par3: function uses no argument 'c'

# test_demo.py::TestParams::test_par1[1-2-3]
# test_demo.py::TestParams::test_par1[1-3-4]
# test_demo.py::TestParams::test_par1[2-5-7]
# test_demo.py::TestParams::test_par2[1-2-3]
# test_demo.py::TestParams::test_par2[1-3-4]
# test_demo.py::TestParams::test_par2[2-5-7]
#
# ============================== 6 passed in 0.01s ==============================

2.4 结合 fixture 参数化

  • 代码示例
import pytest


@pytest.fixture(scope="module")
def login(request):
    return request.param


data = [
    {"name": "jan", "age": 28},
    {"name": "rose", "age": 19}
]


@pytest.mark.parametrize("login", data, indirect=True)
def test_login(login):
    name = str(login["name"])
    age = int(login["age"])
    assert name.startswith("j") and age > 25
    # ========================= 1 failed, 1 passed in 0.11s =========================

  • “login”: 这是参数化测试的参数名称,它用于在测试函数中引用参数
  • data: 这是一个包含参数化数据的可迭代对象,它定义了多组参数值。通常是一个列表或元组,每个元素表示一组参数。
  • indirect=True: 这是一个可选参数,它告诉 pytest 在运行测试函数时将参数作为 fixture 解析并注入到测试函数中。通过设置 indirect=True,你可以通过定义一个与参数名称相同的 fixture 函数来提供参数的值

2.5 将参数格式化输出

通过自定义的 ids,可以提高过程参数的可读性

  • 代码示例
import pytest


def add(a, b):
    return a + b


def get_data():
    """
    测试数据列表。
    :return:
    """
    return [[1, 2, 3], [1, 3, 4], [2, 5, 7]]


# 自定义输出信息。
ids = ["{} + {} = {}".format(a, b, c) for a, b, c in get_data()]


@pytest.mark.parametrize("a,b,c", get_data(), ids=ids)
class TestParams:
    def test_par1(self, a, b, c):
        # 断言 a + b 是否等于 c。
        assert add(a, b) == c

# test-demo.py::TestParams::test_par1[1 + 2 = 3]
# test-demo.py::TestParams::test_par1[1 + 3 = 4]
# test-demo.py::TestParams::test_par1[2 + 5 = 7]
#
# ============================== 3 passed in 0.01s ==============================

三、补充说明

还有一些其他参数化的方式。

  • 钩子函数 pytest_generate_testsconftest.py 文件中的一个钩子函数,用于自定义参数化测试用例的生成过程。通过编写自定义的 pytest_generate_tests 函数,可以定义自己的参数化方案或扩展 pytest 框架默认的参数化行为。

四、结束语


“-------怕什么真理无穷,进一寸有一寸的欢喜。”

微信公众号搜索:饺子泡牛奶

你可能感兴趣的:(Python,python,pytest)