Pytest钩子函数获取用例执行情况

本文介绍了如何使用pytest的hook函数收集测试用例执行结果,并结合allure生成详细报告。还展示了统计个人名下测试用例的方法,包括添加别名标记作者及动态参数化fixture的应用实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、背景:

我们平时使用pytest执行完接口自动化后,可能还要对接邮箱现在更多的是到企业微信、钉钉等发送用例执行情况给到同事,本篇主要介绍如何搜集用例的这些执行情况

二、钩子函数:

2.1、创建位置:

项目主路径下-创建conftes.py文件,这样不用做任何钩子函数自动加载

2.2、代码介绍:

在用例执行完成后会自动打印出结果

def pytest_terminal_summary(terminalreporter, exitstatus, config):
    '''
    收集用例执行结果
    :param terminalreporter:
    :param exitstatus:
    :param config:
    :return:
    '''
    print("捕捉用例的通过对象", len(terminalreporter.stats.get("passed", [])))
    print("捕捉用例的失败对象", len(terminalreporter.stats.get("failed", [])))
    print("捕捉用例的跳过对象", len(terminalreporter.stats.get("skipped", [])))
    faild_list = list()
    faild_object = terminalreporter.stats.get("passed", [])
    print("捕捉用例总数", terminalreporter._numcollected)
    print("捕捉所有对象", terminalreporter.stats)
    for i in faild_object:
        print("获取失败用例集对象i:", i)
        print("获取失败用例集对象名称location", i.location)
        faild_list.append(i.location[-1])
        print("获取失败用例集日志", i.longrepr)

2.3、按各域返回用例情况:

def pytest_terminal_summary(terminalreporter, exitstatus, config):
    '''
    收集用例执行结果
    :param terminalreporter:
    :param exitstatus:
    :param config:
    :return:
    '''
    print("捕捉用例的通过对象", len(terminalreporter.stats.get("passed", [])))
    print("捕捉用例的失败对象", len(terminalreporter.stats.get("failed", [])))
    print("捕捉用例的跳过对象", len(terminalreporter.stats.get("skipped", [])))
    passed_list = list()
    _list = []
    _list1 = []
    passed_object = terminalreporter.stats.get("passed", [])
    print("捕捉用例总数", terminalreporter._numcollected)
    print("捕捉所有对象", terminalreporter.stats)

    for i in passed_object:
        print("获取成功用例集对象i:", i)
        item2 = str(i).split("::")[0]
        if 'test_product/credit' in item2:
            _list.append(item2)
        if 'test_product/tax' in item2:
            _list1.append(item2)
        # _list.append(str(i).split("::")[0])
        print("获取成功用例集对象名称location", i.location)
        passed_list.append(i.location[0])
    print(_list)
    print(_list1)
    print(len(_list))
    print(len(_list1))
    print(passed_list)

在这里插入图片描述
返回情况:
在这里插入图片描述

三、pytest结合allure生成测试报告

3.1、用例执行情况查看:

可以结合生成的报告文件-data下的suites.json中获取执行详情进行解析后即可获得,也可以知道每个测试用例集下的情况
在这里插入图片描述

四、统计个人名下的测试用例方法:

4.1、添加用例别名并注明作者:

例如:
@pytest.mark.parametrize(
'case_id, case_name,[(test01,test02)], ids=[‘测试testcase1@张三’])
备注:这样该条用例就会统计到作者为-张三名下
在这里插入图片描述

4.2、钩子函数统计用例到个人:

def pytest_collection_modifyitems(items):
    list_name = []
    list_node = []
    case_number_list = []
    cases_number_dict = {}
    for item in items:
        item.name = item.name.encode("utf-8").decode("unicode_escape")
        item._nodeid = item.nodeid.encode("utf-8").decode("unicode_escape")
        list_node.append(item._nodeid)
        list_name.append(item.name)
        try:
            # 以@开头匹配第一个不是中文的字符串匹配作者
            match_obj = re.findall("(?<=@).*?(?=[\x00-\xff])", str(item.name))
            case_number_list += match_obj
        except:
            continue
    _list = set(case_number_list)
    for i in _list:
        cases_number_dict[f'{i}'] = case_number_list.count(i)
    # print(f"=================={case_number_list}")
    print(f"=======个人名下用例汇总:{cases_number_dict}")
    logger.info(f"测试用例名称:{list_name}")
    logger.info(f"测试用例文件路径:{list_node}")

这样就能根据别名中的用例作者去分别匹配用例个数了,最终结果:{‘张三’:1},{‘作者’:用例个数}

五、pytest.fixture实现动态传参的几种方式:

5.1、用法描述:

#coding=utf-8
import pytest
 
#fixture传单个参数
 
@pytest.fixture()
def login(request):
    name=request.param #request.param就代表参数化的data里的每一个参数
    #print("账号是:{}".format(name))
    return name
 
data=["vince","jerry"]
 
@pytest.mark.parametrize(
    'login',
    data,
    indirect=True
)
def test_01(login):
    print("测试账号是:{}".format(login))
 
if __name__ == '__main__':
    pytest.main(["-s","test_param2.py"])
    
"""
注:
为了提高复用性,我们在写测试用例的时候,会用到不同的fixture,
比如:最常见的登录操作,大部分的用例的前置条件都是登录
假设不同的用例想登录不同的测试账号,
那么登录fixture就不能把账号写死,
需要通过传参的方式来完成登录操作

indirect=True 表示将参数'login'当做一个函数去执行(即参数名要和定义的fixture的函数名一致),而不是一个参数变量,并且将data当做参数传递给该函数
def test_01(login) ,这里的login是获取fixture返回的值
结果:
test_param2.py 测试账号是:vince
.测试账号是:jerry
.
"""

5.2、fixture传多个参数:

#coding=utf-8
import pytest
@pytest.fixture()
def logins(request):
    param=request.param
    #print("账号是:{}".format(name))
    return param
 
data=[{"username":"user1","pd":"123"},
      {"username":"user2","pd":"456"}
      ]
 
@pytest.mark.parametrize(
    'logins',
    data,
    indirect=True
)
def test_01(logins):
    print("测试用户名是:{0}———测试密码是:{1}".format(logins["username"],logins["pwd"]))
 
if __name__ == '__main__':
    pytest.main(["-s","test_param3.py"])
"""
结果:
test_param3.py 测试用户名是:user1———测试密码是:123
.测试用户名是:user2———测试密码是:456
.
"""

5.3、多个fixture叠加装饰器:

#coding=utf-8
import pytest
 
@pytest.fixture()
def input_user(request):
    user=request.param
    return user
 
@pytest.fixture()
def input_pwd(request):
    pwd=request.param
    return pwd 
 
data=[("user1","123"),
      ("user2","456")
      ]
@pytest.mark.parametrize('input_user',data,indirect=True)
@pytest.mark.parametrize('input_pwd',data,indirect=True)
def test_01(input_user,input_pwd):
    print("测试用户名是:{0}———测试密码是:{1}".format(input_user,input_pwd))
 
if __name__ == '__main__':
    pytest.main(["-s","test_param5.py"])
"""
结果:
test_param5.py 测试用户名是:('user1', '123')———测试密码是:('user1', '123')
.测试用户名是:('user2', '456')———测试密码是:('user1', '123')
.测试用户名是:('user1', '123')———测试密码是:('user2', '456')
.测试用户名是:('user2', '456')———测试密码是:('user2', '456')
.
"""

使用pytest.fixture()实行动态传参

先在conftest.py文件内创建pytest.fixture()装饰器,定义的函数中需要传参request
获取参数方式为:request.param

@pytest.fixture(scope='function', autouse=False)
def tax_prompt(request):
    cert_state = request.param['cert_state']
    prompt_error_msg = request.param['prompt_error_msg']
    rv = kelp_tax()
    rv.common_prompt(cert_state=cert_state, prompt_error_msg=prompt_error_msg)

传递参数方式:
@pytest.mark.parametrize(‘tax_prompt’, [
{‘cert_state’: kelp_tax().prompt_data(num=1)[0][1], ‘prompt_error_msg’: kelp_tax().prompt_data(num=1)[0][0]}],
indirect=True)
在这里插入图片描述

Python的switch case写法:

    @staticmethod
    def prompt_data(num):
        numbers = {
            0: [("testceshi", "46", "46-testceshi@test")],
            1: [("testceshi1", "41", "41-testceshi1@test")],
            2: [("testceshi2", "42", "42-testceshi2@test")],,
            3: [("testceshi3", "43", "43-testceshi@test")],,
            4: "",
            5: "",
            6: ""
        }
        return numbers.get(num, None)

一般情况下需要跟测试用例添加别名的方式:
@pytest.mark.parametrize(‘case_name’, [(‘test01’)], ids=[‘test01@test’])

<think>好的,用户想了解如何使用pytest钩子函数获取测试用例中子测试用例的参数。首先,我需要回忆一下pytest中处理子测试用例的相关机制,尤其是使用@pytest.mark.parametrize的情况,以及子测试通常如何生成。 用户提到的钩子函数,可能需要用到pytest_collection_modifyitems或者pytest_generate_tests这样的钩子。不过,具体到子测试的参数获取,可能是在生成测试项的时候处理的。比如,当使用参数化时,每个参数组合会生成不同的测试用例,而子测试可能指的是这些参数化的用。 接下来,我需要确定如何在这些测试用例被收集或生成时,获取它们的参数。pytest_generate_tests这个钩子函数是在生成测试用例时调用的,可以在这里修改metafunc对象,从而影响测试参数的生成。但用户的需求是获取已经生成的子测试的参数,所以可能需要另一个钩子,比如pytest_itemcollected,它在每个测试项被收集时触发。 不过,pytest中每个测试项的参数信息通常存储在item对象的fixtureinfo或者callspec属性中。如,item.callspec.params可以获取到参数化的参数值。因此,可能需要编写一个钩子函数,在收集测试项时,遍历每个item,检查是否有callspec,然后提取参数。 另外,用户可能需要一个子来具体说明如何实现这一点。比如,在conftest.py文件中定义pytest_collection_modifyitems钩子,遍历所有收集的测试项,并打印出它们的参数。 还需要注意,子测试可能指的是使用unittest中的subTest上下文管理器,但更常见的是参数化测试用例。这里假设用户指的是参数化生成的子测试用例。 总结步骤应该是:创建一个conftest.py文件,使用pytest_collection_modifyitems钩子,遍历items,检查每个item是否有callspec属性,如果有,则提取参数。然后打印或处理这些参数。 可能需要验证这个方法的正确性,比如写一个简单的参数化测试用例,运行pytest时查看是否成功获取参数。同时,需要注意不同pytest版本中钩子的使用是否有差异,确保兼容性。 另外,用户可能需要知道如何访问这些参数,以便进行进一步处理,比如生成自定义报告或过滤测试用例。因此,在回答中应该提供具体的代码示,并解释每个部分的作用,帮助用户理解和应用。 最后,确保回答结构清晰,分步骤说明,并提醒用户注意可能的陷阱,比如钩子函数的正确命名,conftest.py的位置等。</think>在pytest中,通过钩子函数获取测试用例(即参数化生成的用)参数,可以通过以下步骤实现: --- ### 一、核心原理 当使用`@pytest.mark.parametrize`生成参数化测试时,每个子测试用例的元数据会存储在`request`对象的`param`属性或测试项的`callspec.params`中。通过`pytest_collection_modifyitems`钩子可以拦截已收集的测试用例。 --- ### 二、实现步骤 #### 1. 创建`conftest.py`文件 在项目根目录或测试目录中创建该文件,这是pytest默认加载钩子的位置。 #### 2. 编写钩子函数 ```python def pytest_collection_modifyitems(items): for item in items: # 检查是否为参数化用 if hasattr(item, "callspec"): params = item.callspec.params print(f"测试用例 {item.name} 的参数: {params}") ``` #### 3. 参数化测试示 ```python import pytest @pytest.mark.parametrize("a,b,expected", [ (1, 2, 3), (4, 5, 9) ]) def test_add(a, b, expected): assert a + b == expected ``` --- ### 三、运行观察 执行测试时会输出: ``` 测试用例 test_add[1-2-3] 的参数: {'a': 1, 'b': 2, 'expected': 3} 测试用例 test_add[4-5-9] 的参数: {'a': 4, 'b': 5, 'expected': 9} ``` --- ### 四、关键属性解析 - `item.callspec.params`:返回参数字典 - `item.name`:显示包含参数值的完整用名称 - `item.nodeid`:获取唯一路径标识(如`test_sample.py::test_add[1-2-3]`) --- ### 五、高级用法 如需动态修改参数,可使用`pytest_generate_tests`钩子: ```python def pytest_generate_tests(metafunc): if "input" in metafunc.fixturenames: metafunc.parametrize("input", [1, 2, 3]) ``` --- ### 六、注意事项 1. 确保钩子函数放在`conftest.py`中 2. 参数化嵌套时`callspec.params`会包含所有层级参数 3. 通过`item.config`可访问全局配置对象 通过这种方法可以轻松实现测试参数审计、动态过滤用等高级操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

勒布朗-孟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值