接口测试 请教一个 pytest 中 fixture 相关问题:将 fixture 中写入 allure 附件的代码提出来定义成自定义方法后,就无法按预期写入 allure 附件了

耿晓 · 2024年07月02日 · 最后由 耿晓 回复于 2024年07月04日 · 4952 次阅读

我定义了一个 fixture,他本身断言失败后,会在 allure 报告中添加附件,记录这个 fixture 的响应信息。但是为了代码复用,我将断言相关代码提了出来,但是提出来之后发现,如果 fixture 内部断言失败,并不会在 allure 报告中添加附件,请问是为什么?我该怎么解决?

这是原代码,可以正常添加附件,allure 报告效果如下:

@pytest.fixture(scope="session", autouse=True)
def tf_get_trainerId_xiehe(tf_getSession):
    url = Environment.HOST_ADMIN + 'managerList'
    data = {'page': 1,
            'limit': 30,
            'keys': Environment.userphone_tea,
            'characterType': 1,
            'roles': ''}
    resp = tf_getSession.post(url=url, data=data)

    """
    初始想法:我定义了许多fixture,有大部分fixture都需要下面的断言并写allure附件的代码,
    所以为了复用,我期望是将下面的代码提出来,但提出来之后发现就无法成功写入allure附件了。
    """
    allure.step("Check response status code and content")
    try:
        assert resp.status_code == 200, f"Expected status code 200 but got {resp.status_code}"
        assert resp.json()['code'] == 2000, f"Expected code {2000} but got {resp.json()['code']}"
        AssociatedDataTanQiang.id_xiehe_admin = resp.json()['data']['list'][0]['id']
    except (AssertionError,IndexError) as e:
        allure.attach(
            json.dumps(resp.json(), ensure_ascii=False, indent=2),
            name="response.json",
            attachment_type=allure.attachment_type.JSON
        )
        allure.attach(
            resp.text,
            name="response.text",
            attachment_type=allure.attachment_type.TEXT
        )
        raise AssertionError(f"Fixture: {tf_get_trainerId_xiehe.__name__}断言失败") from e

    yield

这是修改后的代码,无法正常在 allure 报告中添加附件:

def check_response(resp, target_position, expected_code=2000):
    allure.step("Check response status code and content")
    try:
        assert resp.status_code == 200, f"Expected status code 200 but got {resp.status_code}"
        assert resp.json()['code'] == expected_code, f"Expected code {expected_code} but got {resp.json()['code']}"
        return target_position
    except (AssertionError,IndexError) as e:
        allure.attach(
            json.dumps(resp.json(), ensure_ascii=False, indent=2),
            name="response.json",
            attachment_type=allure.attachment_type.JSON
        )
        allure.attach(
            resp.text,
            name="response.text",
            attachment_type=allure.attachment_type.TEXT
        )
        raise AssertionError(f"Fixture: 断言失败") from e


@pytest.fixture(scope="session", autouse=True)
def tf_get_trainerId_xiehe(tf_getSession):
    url = Environment.HOST_ADMIN + 'managerList'
    data = {'page': 1,
            'limit': 30,
            'keys': Environment.userphone_tea,
            'characterType': 1,
            'roles': ''}
    resp = tf_getSession.post(url=url, data=data)

    AssociatedDataTanQiang.id_xiehe_admin = check_response(resp,resp.json()['data']['list'][0]['id'])
       yield
最佳回复
耿晓 回复

能理解这个目的,那你这里毕竟把【数据位置】和具体的 json 实例 : resp.json()['data']['list'][0]['id'] 耦合在一起了,得重新设计一下才行

def get_value_by_path(json_data, path):
    for key in path:
        if isinstance(key, int) or key in json_data:
            json_data = json_data[key]
        else:
            return None 
    return json_data

# 传参 path
def check_response(resp, path, expected_code=2000):
    allure.step("Check response status code and content")
    try:
        assert resp.status_code == 200, f"Expected status code 200 but got {resp.status_code}"
        assert resp.json()['code'] == expected_code, f"Expected code {expected_code} but got {resp.json()['code']}"
        # 通过path 拿到数据
        return get_value_by_path(resp.json(), path)
    except (AssertionError,IndexError) as e:
        allure.attach(
            json.dumps(resp.json(), ensure_ascii=False, indent=2),
            name="response.json",
            attachment_type=allure.attachment_type.JSON
        )
        allure.attach(
            resp.text,
            name="response.text",
            attachment_type=allure.attachment_type.TEXT
        )
        raise AssertionError(f"Fixture: 断言失败") from e


@pytest.fixture(scope="session", autouse=True)
def tf_get_trainerId_xiehe(tf_getSession):
    url = Environment.HOST_ADMIN + 'managerList'
    data = {'page': 1,
            'limit': 30,
            'keys': Environment.userphone_tea,
            'characterType': 1,
            'roles': ''}
    resp = tf_getSession.post(url=url, data=data)
    # 自定义 【数据位置】
    target_path = ['data', 'list', 0, 'id']
    AssociatedDataTanQiang.id_xiehe_admin = check_response(resp,target_path)
       yield
共收到 9 条回复 时间 点赞

check_response(resp,resp.json()['data']['list'][0]['id'])
是因为封装后,对 resp 的参数访问被放到了异常处理外层,然后报 IndexError 吗?


就是这么越界的吧

Baoding 回复

不太明白还是,如果想提出来的话应该怎么提呢😄

当前,我把写入 allure 附件的代码从 try 中提出来了,目前不论 fixture 自身报不报错,我都将请求和响应的相关信息写进附件。以下代码可以实现添加附件功能。可是我还是不太明了文中提到的问题原因,难道是因为在 try 中调用自定义方法导致达不到预期效果的?

# 向fixture中添加附件
def add_allure_attachment(url, data, resp):
    allure.step("Check response status code and content")
    allure.attach(
        url,
        name="request_url.txt",
        attachment_type=allure.attachment_type.TEXT
    )
    allure.attach(
        json.dumps(data, ensure_ascii=False, indent=2),
        name="request_data.json",
        attachment_type=allure.attachment_type.JSON
    )
    allure.attach(
        json.dumps(resp.json(), ensure_ascii=False, indent=2),
        name="response.json",
        attachment_type=allure.attachment_type.JSON
    )
    allure.attach(
        resp.text,
        name="response.text",
        attachment_type=allure.attachment_type.TEXT
    )


# 实例化一个session,用于管理后台请求需要验证身份的接口
@pytest.fixture(scope="session")
def tf_getSession():
    url = Environment.HOST_ADMIN + 'loginin'
    data = {
        'username': Environment.userphone_tea,
        'password': Environment.password_tea
    }
    ses = requests.session()
    resp = ses.post(url=url, data=data)

    add_allure_attachment(url=url, data=data, resp=resp)

    try:
        assert resp.status_code == 200, f"Expected status code 200 but got {resp.status_code}"
        assert resp.json()['code'] == 2000, f"Expected code {2000} but got {resp.json()['code']}"
        return ses
    except (AssertionError, IndexError) as e:
        raise AssertionError(f"Fixture 断言失败") from e

这里不是 allure,fixture,异常处理等等特性的原因
简单理解一下
IndexError 就是越界异常,或者说下标找不到
你再看一下 checkresponse 这个入参
resp.json()['data']['list'][0]['id']
这个 resp 数据的 list 已经没有第 1 个 list,也没有 list 中的 id 了

# 传参去掉target_position
def check_response(resp, expected_code=2000):
    allure.step("Check response status code and content")
    try:
        assert resp.status_code == 200, f"Expected status code 200 but got {resp.status_code}"
        assert resp.json()['code'] == expected_code, f"Expected code {expected_code} but got {resp.json()['code']}"
        # target_position在内部的异常处理内再显式调用
        return resp.json()['data']['list'][0]['id']
    except (AssertionError,IndexError) as e:
        allure.attach(
            json.dumps(resp.json(), ensure_ascii=False, indent=2),
            name="response.json",
            attachment_type=allure.attachment_type.JSON
        )
        allure.attach(
            resp.text,
            name="response.text",
            attachment_type=allure.attachment_type.TEXT
        )
        raise AssertionError(f"Fixture: 断言失败") from e


@pytest.fixture(scope="session", autouse=True)
def tf_get_trainerId_xiehe(tf_getSession):
    url = Environment.HOST_ADMIN + 'managerList'
    data = {'page': 1,
            'limit': 30,
            'keys': Environment.userphone_tea,
            'characterType': 1,
            'roles': ''}
    resp = tf_getSession.post(url=url, data=data)
    # 只传resp
    AssociatedDataTanQiang.id_xiehe_admin = check_response(resp)
       yield
7楼 已删除
Baoding 回复

试了下确实可以,但问题是 check_response 中的 return,不同的 fixture 需要返回数据的位置是不一样的,所以起初我才想的是将这个返回数据的位置作为参数传进来,然而作为参数传进来就无法按期望写 allure 附件了。

耿晓 回复

能理解这个目的,那你这里毕竟把【数据位置】和具体的 json 实例 : resp.json()['data']['list'][0]['id'] 耦合在一起了,得重新设计一下才行

def get_value_by_path(json_data, path):
    for key in path:
        if isinstance(key, int) or key in json_data:
            json_data = json_data[key]
        else:
            return None 
    return json_data

# 传参 path
def check_response(resp, path, expected_code=2000):
    allure.step("Check response status code and content")
    try:
        assert resp.status_code == 200, f"Expected status code 200 but got {resp.status_code}"
        assert resp.json()['code'] == expected_code, f"Expected code {expected_code} but got {resp.json()['code']}"
        # 通过path 拿到数据
        return get_value_by_path(resp.json(), path)
    except (AssertionError,IndexError) as e:
        allure.attach(
            json.dumps(resp.json(), ensure_ascii=False, indent=2),
            name="response.json",
            attachment_type=allure.attachment_type.JSON
        )
        allure.attach(
            resp.text,
            name="response.text",
            attachment_type=allure.attachment_type.TEXT
        )
        raise AssertionError(f"Fixture: 断言失败") from e


@pytest.fixture(scope="session", autouse=True)
def tf_get_trainerId_xiehe(tf_getSession):
    url = Environment.HOST_ADMIN + 'managerList'
    data = {'page': 1,
            'limit': 30,
            'keys': Environment.userphone_tea,
            'characterType': 1,
            'roles': ''}
    resp = tf_getSession.post(url=url, data=data)
    # 自定义 【数据位置】
    target_path = ['data', 'list', 0, 'id']
    AssociatedDataTanQiang.id_xiehe_admin = check_response(resp,target_path)
       yield
耿晓 #10 · 2024年07月04日 Author
Baoding 回复

哇,实践了下,可以成功达到预期效果。过程和原因我得在思考消化下,谢谢。
方便加您一下微信吗,😀
GXY1162031010

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册