关键字 开发-12 yaml文件实现参数化

发布时间 2023-12-15 17:47:34作者: dack_deng

前言

说到接口自动化,那肯定少不了参数化,这也是pytest的一个特色之一,相比与unitest实现起来更加方便好用。实验参数化常见的就是使用@pytest.mark.parametrize在测试函数或类中定义多组参数,在用例中实现参
数化。

# 参数化方式一

import pytest
@pytest.mark.parametrize("test_input,expected",[ ["3+5", 8], ["2+4", 6], ["6 * 9", 42] ])
def test_eval(test_input, expected):
  assert eval(test_input) == expected

1. 使用pytest_generate_tests实现参数化


由官方文档,可以知道,实现参数化的方式有三种,我们使用第三种方式进行实验,可以更适合本框架的开发。具体的实现方式如官方详细文档,实现的步骤就是:

1.在test函数中传指定的参数名称
2.conftest中实现hook函数:pytest_generate_tests
3.传入参数化数据,实现参数化

1.1 实现参数化示例

# test_demo3.py
# 用例的id名称
names = ["login nam1", "login name2"]
# 测试数据 list of dict
test_data = [{
    "url": "http://49.235.x.x:5000/api/v1/login/",
    "method": "POST",
    "json": {
    "username": "test",
    "password": "123456"
    }
},
{
    "url": "http://49.235.x.x:5000/api/v1/login/",
    "method": "POST",
    "json": {
    "username": "test",
    "password": "123456"
    }
}]
def test_login(param):
    print('---->', param)
# conftest.py
def pytest_generate_tests(metafunc):
  """ generate (multiple) parametrized calls to a test function."""
  if "param" in metafunc.fixturenames:
    metafunc.parametrize("param",
      metafunc.module.test_data,
      ids=metafunc.module.names,
      scope="function")

2 在yaml中实现参数化

在hook函数中,我们分为module级别的参数化,以及function级别的参数化,module级别就是在yml文件的config中传入的参数,function级别就是在单个用例中传入的参数,所以都支持。
所以可以知道,在yml中要实现参数化,需要解决2个问题:

1.在用例函数中需要传入fixtures参数,也就是需要将参数写在fixtures中,再传入用例函数。
2.在创建函数时,要设置参数属性,以及参数数据属性,即:在生成动态用例函数时,就要添加这2个属性

# conftest.py
def pytest_generate_tests(metafunc):
    """
    测试用例参数化:
    :param metafunc: 共有五个属性值
    1.matafunc.fixturenames: 参数化收集时的参数名称
    2.matafunc.module: 使用参数名称进行参数化的测试用例所在的模块对象
    3.matafunc.config: 测试用例会话
    4.matafunc.function: 测试用例对象,即函数或方法对象
    5.matafunc.cls: 测试用例所属的类的类对象
    :return: None
    """
    if hasattr(metafunc.module, 'module_params_data'):  # 检查函数对象
        params_data = getattr(metafunc.module, 'module_params_data')
        params_fixtures = getattr(metafunc.module, 'module_params_fixtures')

        params_len = 0  # 参数化,参数的个数
        if isinstance(params_data, list):
            if isinstance(params_data[0], list):  # 支持下面这种格式
                """fixtures: username, password
                   parameters:- [test1, '123456']
                              - [test2, '123456']"""
                params_len = len(params_data[0])
            elif isinstance(params_data[0], dict):  # 支持下面这种格式
                """parameters:
                            - {"username": "test1", "password": "123456"}
                            - {"username": "test2", "password": "1234562"}"""
                params_len = len(params_data[0].keys())
            else:
                params_len = 1
        params_args = params_fixtures[-params_len:]

        metafunc.parametrize(
            ','.join(params_args),
            params_data,
            scope="module"
        )
    # 用例级别
    if hasattr(metafunc.module, f'{metafunc.function.__qualname__}_params_data'): # __qualname__:获取用例函数名
        params_data = getattr(metafunc.module, f'{metafunc.function.__qualname__}_params_data')
        params_fixtures = getattr(metafunc.module, f'{metafunc.function.__qualname__}_params_fixtures')
        params_len = 0  # 参数化,参数的个数
        if isinstance(params_data, list):
            if isinstance(params_data[0], list):  # 支持下面这种格式
                """fixtures: username, password
                   parameters:- [test1, '123456']
                              - [test2, '123456']"""
                params_len = len(params_data[0])
            elif isinstance(params_data[0], dict):  # 支持下面这种格式
                """parameters:
                            - {"username": "test1", "password": "123456"}
                            - {"username": "test2", "password": "1234562"}"""
                params_len = len(params_data[0].keys())
            else:
                params_len = 1
        params_args = params_fixtures[-params_len:]
        metafunc.parametrize(
            ','.join(params_args),
            params_data,
            scope="function"
        )

在run.py文件中我们分别添加module级别的和用例级别的代码。

  def run(self):

    ...
    # 收集config中的fixtures
        config_fixtures = self.raw.get('config').get('fixtures', [])
        # 多个则变成list形式存储
        if isinstance(config_fixtures, str):
            config_fixtures = [item.strip(' ') for item in config_fixtures.split(',')]  # 这里做了优化,需要去掉item中的" ",否则fixture传入的不合法

    # 在收集config——fixtures这里,我们添加收集parameters参数data
    config_param_data = self.raw.get('config').get('parameters', [])
    # 2.先渲染config_variables

    ...

    # # --------------给module加参数化-------
    config_param_data = render_template_obj.rend_template_any(config_param_data, **self.context)
    config_param_fixtures = render_template_obj.rend_template_any(config_fixtures, **self.context)
    # # config中支持2种格式,把他们合并然后返回一种统一处理
    config_param_fixtures, config_param_data = self.parameters_data(config_param_fixtures, config_param_data)
    if config_param_data:
          # 向module中加入参数化的属性
          setattr(self.module, 'module_params_data', config_param_data)
          setattr(self.module, 'module_params_fixtures', config_param_fixtures)
     # # -------------------end----------------

    ...
    # 在用例这里,添加用例级别的代码
    case = {}  # 收集用例名称和执行内容
    for case_name, case_value in self.raw.items():
      ...
    # 用例中的fixtures
    case_fixtures = []
    if 'fixtures' in case[case_name][0]:  # fixtures写在第一个步骤中
        case_raw_fixtures = case[case_name][0].get('fixtures', [])
        case_fixtures = render_template_obj.rend_template_any(case_raw_fixtures, **self.context)
        # 如果有多个,则变成list的形式
        if isinstance(case_fixtures, str):  # fixtures: run_fixt or [run_fixt1, run_fixt2] --> list  # 这里做了修改优化,放到了if层级下
            case_fixtures = case_fixtures.split(',')
    # --------------用例级别的参数化----------
    if 'parameters' in case[case_name][0]:
        case_raw_parameters = case[case_name][0].get('parameters', [])
        case_parameters = render_template_obj.rend_template_any(case_raw_parameters, **self.context)
        case_fixtures, case_parameters = self.parameters_data(case_fixtures, case_parameters)
        if case_parameters:
            # 向module中加入参数化数据和属性
            setattr(self.module, f'{case_name}_params_data', case_parameters)
            setattr(self.module, f'{case_name}_params_fixtures', case_fixtures)
    # -------------end--------------------

    # 新增函数:对传入的2种方式,做了统一处理,统一返回:参数(传入的fixtures),value(参数数据)的形式
    def parameters_data(self, fixtures, parameters):
        """
        参数化实现2种方式:
        方式1:
        config:
        name: post示例
        fixtures: username, password
        parameters:
            - [test1, '123456']
            - [test2, '123456']
        方式2:
        config:
        name: post示例
        parameters:
            - {"username": "test1", "password": "123456"}
            - {"username": "test2", "password": "1234562"}
        :returns 统一合并:key和value
            fixtures: 用例需要用到的fixtures: ['username', 'password']
            parameters: 参数化的数据list of list : [['test1', '123456'], ['test2', '123456']]
        """
        #1.将fixtures的str换成list
        if isinstance(fixtures, str):
            fixtures = [item.strip(' ') for item in fixtures.split(',')]
        if isinstance(parameters, list) and len(parameters) >= 1:  # 收集方式二中的key和value
            if isinstance(parameters[0], dict):
                params = list(parameters[0].keys())
                new_parameters = []
                for item in parameters:
                    new_parameters.append(list(item.values()))
                for param in params:
                    if param not in fixtures:
                        fixtures.append(param)
                return fixtures, new_parameters
            else:
                return fixtures, parameters
        else:
            # 没有参数化数据,直接返回[]
            fixtures, []

上面增加的代码解决了:1.将参数作为fixtures传入到用例函数中,2.动态生成用例函数前,先增加module中的属性:参数名和参数数据
创建test_x.yml文件

config:
  fixtures: user, psw
  parameters:
    - [a, 'b']
    - [c, d]

test_x1:
  name: 用例1
  parameters:
    - {"username": "test1", "password": "123456"}
    - {"username": "test2", "password": "1234562"}
  print: "1-${user}"

test_x2:
  name: 用例2
  print: "2-${user}"

由参数化数据,可知,该yml文件参数化,生成6条case。运行:pytest .\test_x.yml