瓜瓜
瓜瓜
发布于 2024-02-05 / 23 阅读
0
0

基于Unittest框架的接口自动化断言封装

引言

我在编写接口测试用例时发现,我们如果使用unittest 提供的原生断言方法的断言成本很高,而且代码可读性变得比较差,因此我想在common模块中对断言方法进行封装。

 ## 案例
   def testCase01_major(self):
        """新建分组主流程测试用例"""
        host = 'http://******'
        url = host + '******************************'
        user_id = 372199862
        headers = {
            'x-user-key': str(user_id),
            'cookie:' **********'
        }
        group_id = str(int(time.time()))
        group_name = f'group_{int(time.time())}'
        body = {
            'groupId':  group_id,
            'groupName':  group_name,
            'order': 0
        }
        res = requests.post(url=url, headers=headers, json=body)
        self.assertEqual(200, res.status_code)
        self.assertTrue('responseTime' in res.json().keys())
        self.assertTrue('updateTime' in res.json().keys())
        self.assertTrue(len(res.json().keys()) == 2)
        self.assertTrue(type(res.json()['responseTime']) == int)
        self.assertTrue(type(res.json()['updateTime']) == int)

在上述代码中,我基于请求行营的状态码,以及响应体中参数进行了校验。从以上代码来看,存在的问题有:断言代码冗余,断言失败后不能明确具体是什么问题,复用性差等问题。 所以,基于以上问题,需要优化断言方法,将断言方法封装。

断言方法封装思路

首先,我们需要明确封装断言方法要实现的目标:

  1. 支持多层级字段校验(基础类型、嵌套字典、列表结构)

  2. 灵活控制校验严格性(比如是否允许多余字段)

其次,基于断言方法封装的目标,断言方法封装思路主要就是进行返回体中json字符串的对齐。

因此,返回体需要校验的内容如下

  1. 接口文档中所描述的返回字段是否存在

    • 确保返回体中所有字段都能成功返回。

  2. 返回体中不存在除接口文档外的多余字段

  3. 返回体中字段的数据类型

    • 对于已知并且有基准值的字段,可以直接判断其基准值。

  4. 数据源校验,前置构建的数据和查询到的数据需要一一对齐

  5. “子对象”也需要校验

    • 例如,部分返回体中存在嵌套列表结构的数据时,我们需要对嵌套列表中的内容也进行校验。

断言方法封装步骤

step1: 定义期望返回体的协议

基于在“引言”中给出的案例,我们可以根据实际返回体定义出 期望的返回体如下:

{
    "responseTime":long,
    "updateTime": long
}

【注意事项】

  • 在定义期望对齐返回体时需要明确返回体中的字段和结构

  • 仅需要校验数据类型的字段时,在预期返回体对应参数的值直接描述数据类型,比方说 "responseTime": int

  • 需要同时校验数据类型和精准度值的字段时,直接在预期返回体对应参数的值描述精准值即可,比方说"responseTime": 1

  • 如果存在嵌套结构,也需要一一对齐数据结构和值描述在预期值当中。

step2:实现方法框架以及字段的遍历

基于unittest框架使用unittest下的断言方法实现封装。

import unittest

class AssertCommon(unittest.TestCase):
    def func(self, expect, actual):
        """
        json通用断言方法
        :param expect: 定义预期返回体
        :param actual: 实际返回的json
        :return: 断言成功返回None,断言失败触发fail
        """
        for key, value in expect.items():
            self.assertIn(key, actual.keys())

step3: 检测是否存在协议外的其他字段

在step2 中的判断,只能校验出返回体中存在的字段,但是对于返回体中多余的字段无法判断。

# 校验是否存在多余的字段
        self.assertEqual(len(expect.keys()), len(actual.keys()),        msg=f'response keys len different, response keys have: {list(actual.keys())}')

step5:实现字段类型的校验

需要先判断预期中值是否为类型,才进行类型校验

        for key, value in expect.items():
            # 进行数据类型的校验
            if isinstance(value, type):
                self.assertEqual(value, type(actual[key]),
                                 msg=f'{key} type error! actual type is {str(type(actual[key]))}')

step6:实现字段精准值的校验

            else:
                self.assertEqual(value, actual[key],
                                 msg=f'{key} value error! actual value is {str(actual[key])}')

step7:遇到嵌套的列表结构时

也需要进行类型的校验和精准值的校验,根据列表索引找到对应值

            elif isinstance(value, list):
                for i in range(len(value)):
                    if isinstance(value[i], type):
                        self.assertEqual(value[i], type(actual[key][i]),
                                         msg=f'list element {actual[key][i]} type different, actual response {actual[key]}')
                    else:
                        self.assertEqual(value[i], actual[key][i],
                                         msg=f'list element {actual[key][i]} value different, actual response {actual[key]}')

step8:遇到列表字典的嵌套结构,需要实现递归

            elif isinstance(value, list):
                for i in range(len(value)):
                    if isinstance(value[i], type):
                        self.assertEqual(value[i], type(actual[key][i]),
                                         msg=f'list element {actual[key][i]} type different, actual response {actual[key]}')
                    elif isinstance(value[i], dict):
                        self.func(value[i], actual[key][i])

总结

经过断言封装后,在测试用例中我们可以通过直接调用AssertCommon().fun() 实现断言,而不需要在每个用例中都执行self.assertin/ self.assertEqual等方法。进而避免了代码冗余。而且在之后如果返回体有更新时,测试用例也易于维护。

# 封装前:冗余断言  
self.assertEqual(200, res.status_code)  
self.assertTrue('responseTime' in res.json().keys())  
...  

# 封装后:单行调用  
AssertCommon().func(expect.res, res.json())  


评论