• 用 Python 为接口测试自动生成用例


    用Python为接口自动生成测试用例

    基于属性的测试会产生大量的、随机的参数,特别适合为单元测试和接口测试生成测试用例

    尽管早在2006年haskell语言就有了QuickCheck来进行”基于属性的测试“,但是目前来看这依然是一个比较小众的领域,参考资料有限,本文如有不足,欢迎指正。


    1. 基于表的测试

    在过去的测试实践中,执行测试时通常需要明确的内容(Value):

    1. 条件
    2. 输入
    3. 结果

    这些内容可以通过”判定树“或者”判断表“来表示,然后测试的执行过程变成了这样

    “给定输入 X,我们期望 Y”

    可以称为 基于表的测试

    在最初,这给了我们测试的方向,但是缺点也非常明显:

    你要足够多的"X->Y" 才能可能覆盖到隐蔽的bug。

    这里请大家回答几个问题:

    1. 你是否已经为每一个测试编写了足够多的用例,以至于你十分确定真的不需要再增加用例了
    2. 这么多的用例,你编写的是否开心?是否高效?是否愿意继续坚持?

    如果以上问题的答案不是yes,那么基于属性的测试就是你需要掌握的东西!


    2. 基于属性的测试

    基于属性的测试和基于表的测试,最大的区别可以这样描述:

    “给定输入 X值,我们期望 Y值”

    vs

    “给定输入 X类,我们期望 Y类”

    于是利用工具生成大量的X类数据,进行测试,并验证结果是否Y类。

    值得注意的是:

    1. 关注输入的类型,而不是输入的值
    2. 根据类型自动生成大量的、随机的输入值

    换言之,它在自动的生成测试用例,虽然输入值是随机的,但是值得类型符合规范要求


    3. 如何进行基于属性的测试

    在不同的语言中有不同的工具来实现,比如:

    • haskell中的QuickCheck
    • java中的quicktheories
    • python中的hypothesis

    本文以python为例进行演示:

    假设有add函数,接收两个类型整数参数,并返回它们的相加结果

    def add(a: int, b: int) -> int:
        pass
    

    首先写出一个简单的测试用例

    def test_add():
        assert 3 == add(1, 2)
    

    正如前面所说,一个这样的用例,根本没信心覆盖全部的场景,例如:

    • 参数是0
    • 参数是负数
    • 参数值特别大
    • 其他...

    所以接下来怎么办?

    改为基于属性的测试

    import hypothesis.strategies as st
    from hypothesis import given
    
    @given(st.integers(), st.integers())
    def test_add(a, b):
        c = add(a, b)
        print(f"{a=},{b=}, {c=}")
        assert isinstance(c, int)
        assert c == a + b
    

    执行结果

     a=0,b=0, c=0
    a=0,b=0, c=0
    a=0,b=0, c=0
    a=16926,b=11053, c=27979
    a=0,b=0, c=0
    a=0,b=0, c=0
    a=21010,b=-2732672789497425072, c=-2732672789497404062
    a=0,b=0, c=0
    a=14554,b=-15956, c=-1402
    a=15597,b=0, c=15597
    a=15597,b=13, c=15610
    a=15597,b=0, c=15597
    a=15597,b=15597, c=31194
    a=28965,b=-36, c=28929
    a=113,b=-36, c=77
    a=28929,b=-36, c=28893
    a=28929,b=9356, c=38285
    a=9356,b=9356, c=18712
    a=10278,b=-62, c=10216
    a=-62,b=-62, c=-124
    a=1625,b=-68244995710046113596363052355575247332, c=-68244995710046113596363052355575245707
    a=1625,b=-13143, c=-11518
    a=1625,b=1625, c=3250
    a=-1940,b=-31868, c=-33808
    a=-7,b=1, c=-6
    a=-1,b=0, c=-1
    a=-1,b=0, c=-1
    a=13,b=7245, c=7258
    a=13,b=13, c=26
    a=-99,b=-18, c=-117
    a=-30172,b=66, c=-30106
    a=-30172,b=-16940, c=-47112
    a=-16940,b=-16940, c=-33880
    a=-1233214851,b=-5152, c=-1233220003
    a=-5152,b=-5152, c=-10304
    a=-16,b=-29706, c=-29722
    a=-29706,b=-29706, c=-59412
    a=-29706,b=116, c=-29590
    a=29696,b=90, c=29786
    a=29696,b=29696, c=59392
    a=-11446,b=-21185, c=-32631
    a=-21185,b=-21185, c=-42370
    a=-12,b=16437, c=16425
    a=-12,b=16437, c=16425
    a=3202,b=53, c=3255
    a=3202,b=3202, c=6404
    a=-98,b=3, c=-95
    a=82691970030325711417874227410289695610,b=1316378701, c=82691970030325711417874227411606074311
    a=82691970030325711417874227410289695610,b=82691970030325711417874227410289695610, c=165383940060651422835748454820579391220
    a=24100,b=14385, c=38485
    a=24100,b=14385, c=38485
    a=24100,b=24100, c=48200
    a=12293,b=-106, c=12187
    a=12293,b=27280, c=39573
    a=27280,b=27280, c=54560
    a=-18887,b=-5530, c=-24417
    a=-18887,b=-18887, c=-37774
    a=4738,b=122351151658095310625663643505383743930, c=122351151658095310625663643505383748668
    a=-24601163521689169516616964879873921492,b=565838202, c=-24601163521689169516616964879308083290
    a=4738,b=122351151658095310625663643281738736058, c=122351151658095310625663643281738740796
    a=-96098295006598318424285019062007505,b=25207226, c=-96098295006598318424285019036800279
    a=-96098295006598318424285019062007505,b=-1997122225172868107163535967078611096, c=-2093220520179466425587820986140618601
    a=-96098295006598318424285019062007505,b=-96098295006598318424285019062007505, c=-192196590013196636848570038124015010
    a=-36,b=1190, c=1154
    a=-36,b=-4, c=-40
    a=-36,b=-36, c=-72
    a=2047897602,b=-4641, c=2047892961
    a=2047897602,b=2047897602, c=4095795204
    a=-1307873608,b=11753, c=-1307861855
    a=-1307873608,b=-3308225400997338452, c=-3308225402305212060
    a=-1307873608,b=770256249, c=-537617359
    a=-1307873608,b=-1307873608, c=-2615747216
    a=-4715910568460396013,b=-18622, c=-4715910568460414635
    a=16754,b=-6053, c=10701
    a=-6053,b=-6053, c=-12106
    a=-6053,b=-6053, c=-12106
    a=-22264,b=44, c=-22220
    a=-22264,b=-22264, c=-44528
    a=-86,b=-86, c=-172
    a=-86,b=-86, c=-172
    a=1794,b=28170, c=29964
    a=1794,b=28170, c=29964
    a=-93,b=482, c=389
    a=-1,b=482, c=481
    a=-1,b=-1, c=-2
    

    由结果可知,工具根据参数是整数这一规范,自动生成、执行了大量的测试用例


    4. 在接口测试中自动生成用例

    接口测试和函数的单元测试非常相似:

    • 输入数据
    • 返回数据

    此外接口文档作为前后端、甚至测试开发的对接窗口,对参数的要求约定的更加细致,

    以OpenAPI为例,每个参数可以有以下属性:

    • type:数字还是字符串?
    • format:密码还是电子邮箱?
    • maxLength:长度不超过多少?
    • required:是否必填?
    • in:参数通过什么传递?
    • 其他...

    于是为接口生成符合要求的参数就变得可行了,举个例子:

    import logging
    import unittest
    
    import requests
    
    from api_tools import APITestCase
    
    
    
    class UserCase(APITestCase):
        schema = "http://127.0.0.1:7600/openapi.json"
    
    if __name__ == "__main__":
        unittest.main()
    

    这是以unittest为例进行封装的结果,只需要在TestCase中指定openapi的内容(或路径),

    启动测试框架时,会自动读取、解析接口文档,并生成测试用例

    下面是部分执行日志,可以看到对接口发送了随机参数,并获得返回值

    INFO     2022-04-10 01:02:59,223 : 执行用例 login_login_access_token_post
    INFO     2022-04-10 01:02:59,223 : -----调用接口:login_login_access_token_post-----
    INFO     2022-04-10 01:02:59,223 : 发送请求>>>   :请求数据 = {'userin': {'password': 'bEYtwDZUxdBVThEFOqTz', 'email': 'KEfQnAtTNtwZOXRjoIjp'}}
    INFO     2022-04-10 01:02:59,578 : 接收响应   <<<: 
    INFO     2022-04-10 01:02:59,578 : -----调用完毕:login_login_access_token_post-----
    INFO     2022-04-10 01:02:59,578 : 执行用例 test_token_login_test_token_post
    INFO     2022-04-10 01:02:59,578 : -----调用接口:test_token_login_test_token_post-----
    INFO     2022-04-10 01:02:59,578 : 发送请求>>>   :请求数据 = {}
    INFO     2022-04-10 01:03:00,035 : 接收响应   <<<: 
    INFO     2022-04-10 01:03:00,035 : -----调用完毕:test_token_login_test_token_post-----
    INFO     2022-04-10 01:03:00,035 : 执行用例 sign_up_login_sign_up_post
    INFO     2022-04-10 01:03:00,035 : -----调用接口:sign_up_login_sign_up_post-----
    INFO     2022-04-10 01:03:00,035 : 发送请求>>>   :请求数据 = {'userin': {'password': 'GgzYcOwapTwnkkKVTraE', 'email': 'EJrXlCSNKKjdiVvAOnTM'}}
    INFO     2022-04-10 01:03:00,657 : 接收响应   <<<: 
    INFO     2022-04-10 01:03:00,657 : -----调用完毕:sign_up_login_sign_up_post-----
    INFO     2022-04-10 01:03:00,657 : 执行用例 todo_list_todo_get
    INFO     2022-04-10 01:03:00,657 : -----调用接口:todo_list_todo_get-----
    INFO     2022-04-10 01:03:00,657 : 发送请求>>>   :请求数据 = {}
    INFO     2022-04-10 01:03:01,233 : 接收响应   <<<: 
    INFO     2022-04-10 01:03:01,233 : -----调用完毕:todo_list_todo_get-----
    INFO     2022-04-10 01:03:01,233 : 执行用例 todo_post_todo_post
    INFO     2022-04-10 01:03:01,233 : -----调用接口:todo_post_todo_post-----
    INFO     2022-04-10 01:03:01,233 : 发送请求>>>   :请求数据 = {'todoin': {'title': '', 'is_done': False}}
    INFO     2022-04-10 01:03:01,594 : 接收响应   <<<: 
    INFO     2022-04-10 01:03:01,594 : -----调用完毕:todo_post_todo_post-----
    INFO     2022-04-10 01:03:01,594 : 执行用例 todo_delete_all_todo_delete
    INFO     2022-04-10 01:03:01,594 : 执行用例 todo_get_todo__todo_id__get
    INFO     2022-04-10 01:03:01,594 : -----调用接口:todo_get_todo__todo_id__get-----
    INFO     2022-04-10 01:03:01,594 : 发送请求>>>   :请求数据 = {'todo_id': 2451}
    INFO     2022-04-10 01:03:02,026 : 接收响应   <<<: 
    INFO     2022-04-10 01:03:02,026 : -----调用完毕:todo_get_todo__todo_id__get-----
    INFO     2022-04-10 01:03:02,026 : 执行用例 todo_put_todo__todo_id__put
    INFO     2022-04-10 01:03:02,026 : -----调用接口:todo_put_todo__todo_id__put-----
    INFO     2022-04-10 01:03:02,026 : 发送请求>>>   :请求数据 = {'todo_id': 1519, 'todoin': {'title': '', 'is_done': False}}
    INFO     2022-04-10 01:03:02,423 : 接收响应   <<<: 
    INFO     2022-04-10 01:03:02,423 : -----调用完毕:todo_put_todo__todo_id__put-----
    INFO     2022-04-10 01:03:02,423 : 执行用例 todo_delete_todo__todo_id__delete
    
    

    鉴于篇幅有限,暂时介绍这么多,
    文章首发与我的公众号测试开发研习社,如果你对这个话题依旧有兴趣,可以回复或评论:PBT

    原创不易,欢迎关注,不错过技术干货,谢谢鼓励!

  • 相关阅读:
    传统分拣弊端明显,AI机器视觉赋能物流行业包裹分类产线数智化升级
    【IPC 通信】信号处理接口 Signal API(4)
    # 我实践:用docker registry API 获取清单并删除某仓库某tag镜像
    DFT Compiler极简示例1
    Flink学习第九天——Flink里面核心Source Sink对接 Kafka Connetor实战
    (5)点云数据处理学习——其它官网例子2
    element table多级表头
    kernel简单学习(CTF-wiki)
    巧用.bat批处理文件
    Flowable 的使用
  • 原文地址:https://www.cnblogs.com/dongfangtianyu/p/api_test_by_pbt.html