menu dzf
search self_improvement
目录
利用 Python 实现通用断言方法提升编码效率
dzf
dzf 2022年03月21日  ·  阅读 212

在软件开发尤其是接口测试相关的工作中,我们常常需要对接口返回的结果进行各种断言,以确保其符合预期。如果每次都针对不同接口、不同的返回情况去编写特定的断言逻辑,那无疑会耗费大量时间且代码复用性极低。今天就来和大家分享一下如何实现一个通用的断言方法,以此提高我们的编码效率。

一、接口需要断言的内容

在进行接口返回结果的断言时,通常需要关注以下几个关键方面:

  1. 返回状态码
    接口返回的状态码能直观反映此次请求的处理情况,比如常见的 200 表示成功,其他不同的状态码对应着不同的错误或提示信息,所以它是我们首要校验的内容。
  2. 返回字段的对齐
    接口返回的数据结构中,字段的数量和定义需要与我们预期的一致,不能出现缺少或者多余的字段,这样才能保证后续对具体数据的处理不会出现问题。
  3. 不能有多余的字段出现
    确保实际返回的数据中不存在我们未预期的额外字段,保证数据的规范性和可预测性,避免因多余字段引发后续数据处理的异常。
  4. 返回字段数据类型的对齐
    每个字段对应的数据类型也必须符合预期,例如某个字段预期是整数类型,那实际返回的就不能是字符串等其他类型,数据类型的正确与否直接关系到后续业务逻辑对数据的操作能否正确执行。
  5. 返回值的精准校验
    这里涉及到多个细节,像前置构建的唯一性字段,要保证其唯一性符合要求;默认值的校验,确保在没有特定赋值时返回正确的默认值;构建数据时前置描述的必填项,这些必填内容必须存在;构建数据时的可选项也得符合设定。对于动态值的返回,通常不需要精准值校验,但如果是有固定的生成规则,比如某一种数据加密的生成方式,这时我们可以校验数据的加密逻辑是否符合预期。

二、通用断言方法实现思路

在 Python 中,我们可以利用递归的知识来实现通用的断言方法。递归简单来说就是在函数中调用自身函数,这两个函数是同一个函数。通用的断言需要通过递归实现列表字典子对象的断言方法,这样就能层层深入地对复杂的数据结构进行全面的校验。
下面是基于 Python 的 unittest 框架实现的通用断言类的代码示例:

import unittest

class GeneralAssert(unittest.TestCase):
    def http_assert(self,expected,actual):
        if isinstance(expected,dict):
            self.assertEqual(len(expected.keys()),len(actual.keys()),msg=f"{list(expected.keys())}不符,实际的key是{list(actual.keys())}")
            for key,value in expected.items():
                if isinstance(value,type):
                    self.assertEqual(value,type(actual[key]),msg=f'{key}的类型不符,实际的类型是{type(actual[key])}')
                elif isinstance(value,dict):
                    self.http_assert(value,actual[key])
                elif isinstance(value,list):
                    for i in range(len(expected[key])):
                        if isinstance(expected[key][i],type):
                            self.assertEqual(expected[key][i],type(actual[key][i]),msg=f'{expected[key]}类型不符,实际的类型是{actual[key][i]}')
                        if isinstance(expected[key][i],(dict,list)):
                            self.http_assert(expected[key][i],actual[key][i])
                        else:
                            self.assertEqual(expected[key][i],actual[key][i],msg=f'{expected[key]}的值不同,实际的值是{actual[key][i]}')
                else:
                    self.assertEqual(value,actual[key],msg=f'期望{key}值{value}不同,实际的值是{actual[key]}')
        if isinstance(expected,list):
            self.assertEqual(len(expected),len(actual),msg=f'与{list(expected)}字段个数不一致,实际的字段为{list(actual)}')
            for i in range(len(expected)):
                if isinstance(expected[i],(dict,list)):
                    self.http_assert(expected[i],actual[i])
                else:
                    self.assertEqual(expected[i],actual[i],msg=f'{expected[i]}的值错误,实际值为{actual[i]}')
            

class TestHttpAssert(GeneralAssert):
    def test_http_assert(self):
        expect = {
            'status': 'success',
            'dict': {'a': 'b', 'c': 'd'},
            'responseTime': int,
            'data': [
                {'id': 1, 'name': 'Alice'},
                {'id': 2, 'name': 'Bob'}
            ]
        }
        actual = {
            'status': 'success',
            'dict': {'a': 'b', 'c': 'd'},
            'responseTime': 123,
            'data': [
                {'id': 1, 'name': 'Alice'},
                {'id': 2, 'name': 'Bob'}
            ]
        }
        self.http_assert(expect, actual)
if __name__ == '__main__':
    unittest.main()

在上述代码中,针对字典结构的断言逻辑已经有了详细的实现,主要步骤如下:

  • 首先校验字典的键的数量是否一致,如果不一致则给出相应提示信息。

  • 接着遍历字典的键,确保每个预期的键都存在于实际返回结果中。

  • 然后根据值的类型进行不同处理:

    如果值的类型是 type(也就是用来判断数据类型的情况),则校验实际返回值的数据类型与预期是否相符。
    如果值是列表类型,进一步校验列表长度是否一致,并且对列表中的每个元素再根据其类型(是类型判断、字典、列表还是普通值等情况)进行递归或者直接值的比较断言。
    如果是普通值,则直接比较预期值和实际值是否相等。
    

对于列表结构的断言部分逻辑如下:

  • 先校验列表的整体长度是否一致。

  • 然后遍历列表中的每个元素,同样根据元素的类型进行不同操作:

    如果元素类型是 type,校验其类型与实际返回元素的类型是否一致。
    如果元素是字典类型,就调用自身的 http_assert 方法进行递归断言,因为字典内部可能还有复杂的嵌套结构需要校验。
    如果元素是列表类型,再次校验子列表的长度是否一致,并对子列表中的元素按照其类型进行递归或者值的比较断言。
    如果是普通元素值,则直接比较预期值和实际值是否相等。
    

希望这篇文章和代码示例对大家在接口测试或者其他需要数据断言的场景下有所帮助,大家可以根据实际需求进一步扩展和优化这个通用断言方法哦。

分类:
标签: