接口测试 Http 接口测试框架 (开源 + 已投入实际项目中)

Heyniu · 2016年08月08日 · 最后由 PimLee 回复于 2023年08月09日 · 4922 次阅读
本帖已被设为精华帖!

相关链接

说明

由于部分内容涉及公司机密,已用字母替换,不影响阅读

实际效果

  • 验证 1000 个接口平均耗时 6s(看机器配置及网速)
  • 第一次投入使用,马上发现 5 个接口异常并且该验证过程不到 30s 的时间

框架的下一步

  • 目前已兼容我们公司所有 app

  • response body 全字段验证(含字段类型)

    • 已有思路
  • 字段变化导致 2 个大问题

    • 由原本 int(0/1)变成了布尔型
    • 时间戳长度由 10 位突然变成 13 位
  • 尽量避免日常监控中跑接口对外网数据/用户的影响

    • 目前做法是屏蔽相关接口
    • 目前状态
    • 跑接口时创建的数据 id 有变化,然而删除接口还是调用老的 id 去删除,导致数据删除不到
    • 改进
    • 拦截创建数据的 response body 取出对于 id
    • 拦截删除接口 request body,传入上一步拦截的 id

最新框架图(红色部分未完成)

Http接口测试框架

部分代码

配置文件

http接口测试框架配置信息

tester = tester
project = A
versionName = 2.2.2
versionCode = 237
host = a-webapi.test.b.com
getTokenHost = http://a-webapi.test.b.com/api/System/GetToken
loginHost = http://a-webapi.test.b.com/api/User/LoginV2
loginInfo = Phone=13750199962&Password=FGgIwe5oCdk%3D
SessionsPath = D:\Fiddler Sessions
ApiURL = http://apihelper.b.com/Home/API/c
# SpecialSessions >> 屏蔽的接口
SpecialSessions = ['GetToken', 'LoginV2', 'LogOut', 'BookingV2']

启动测试入口

"""
运行api测试总入口
"""

import sessions.Request


def launcher_api_test():
    """
    1.获取接口列表
    2.与本地sessions对比
    3.差异化文件,是否继续
    3.1否 继续录制接口
    3.2是 开始跑接口
    :return:
    """
    r = sessions.Request.Request(0)  # 0 >> A    1 >> B
    r.start()


if __name__ == "__main__":
    launcher_api_test()

头部、登录接口

def __get_token_header(self):
    """
    生成token头部
    :return:
    """
    des = self.__get_token_des()
    arr = (des, self.conf['systemType'], self.conf['Model'], self.conf['Release'], self.conf['DeviceId'],
           self.conf['versionCode'], self.conf['versionName'], self.conf['AppBuild'], self.conf['DeviceOS'], "0")
    authorization = self.AUTHORIZATION_TOKEN % arr
    headers = {'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', 'Authorization': authorization}
    response = self.session.post(self.conf['getTokenHost'], headers=headers)
    if json.loads(response.text)['StatsCode'] == 200:
        data1 = json.loads(response.text)['Data']
        self.time = data1['Time']
        self.TOKEN_NAME = data1['TokenName']
        self.TOKEN_VALUE = data1['TokenValue']
    else:
        print("GetToken失败,请手动检查")
        utils.HandleJson.HandleJson.print_json(response.text)

def __login_session(self):
    """
    调用登录接口,这样后面的接口都能正常访问了
    :return:
    """
    url_login = self.conf['loginHost']
    headers = self.__get_session_header(url_login.split('api/')[-1])
    data_login = r'%s' % self.conf['loginInfo']
    response = self.session.post(url_login, headers=headers, data=data_login)
    if json.loads(response.text)['StatsCode'] == 200:
        data1 = json.loads(response.text)['Data']
        self.uId = data1[self.head_uid]
        self.uName = data1['NickName']
        self.uPhone = data1['Phone']
        self.SessionId = data1['Sid']
        self.uType = data1['UserType']
        self.uuid = data1['UID']
    else:
        print("登录失败,请手动检查")
        utils.HandleJson.HandleJson.print_json(response.text)

接口片段

Request url: a-webapi.test.b.com/api/Circle/AddCancelCollectCircle
Request header: POST /api/Circle/AddCancelCollectCircle HTTP/1.1
Host: a-webapi.test.b.com
Accept: text/json
Authorization: Digest t="2016-08-04 16:41:19",SystemType="2",u="Circle/AddCancelCollectCircle",r="59e93eb1a6625adc6bff5ede5945a2f7",DeviceId="ffffffff-8416-49fe-3fdc-6ee400000000",Model="SM-N9100",DeviceOS="22",Release="5.1.1",VersionName="2.2.2",VersionCode="239",PushToken="",uId="3353",uName="123456ejz",uPhone="13750199962",SessionId="%2FZhckUf9%2Fd2soQZhYofjN021SdWUpLv0aW%2F3CJBr71vtOl1YHJda6J8p6P1hsQS0P3kqirm%2BtPs%3D",uType="1",bDChannelId="",bDUserId="",AppBuild="239",uUID="2255"
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Content-Length: 21
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.0.1

Request body: CircleId=6418&IsAdd=1
Response code: 200
Response body: {"StatsCode":200,"Message":"收藏成功","Data":null,"Other":null}
Session end



Request url: a-webapi.test.b.com/api/Circle/AddCancelCollectCircle
Request header: POST /api/Circle/AddCancelCollectCircle HTTP/1.1
Host: a-webapi.test.b.com
Accept: text/json
Authorization: Digest t="2016-08-04 16:41:22",SystemType="2",u="Circle/AddCancelCollectCircle",r="26dcb55ee9ac79995c21656517c455e8",DeviceId="ffffffff-8416-49fe-3fdc-6ee400000000",Model="SM-N9100",DeviceOS="22",Release="5.1.1",VersionName="2.2.2",VersionCode="239",PushToken="",uId="3353",uName="123456ejz",uPhone="13750199962",SessionId="%2FZhckUf9%2Fd2soQZhYofjN021SdWUpLv0aW%2F3CJBr71vtOl1YHJda6J8p6P1hsQS0P3kqirm%2BtPs%3D",uType="1",bDChannelId="",bDUserId="",AppBuild="239",uUID="2255"
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Content-Length: 21
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.0.1

Request body: CircleId=6418&IsAdd=0
Response code: 200
Response body: {"StatsCode":200,"Message":"取消收藏成功","Data":null,"Other":null}
Session end

接口列表

接口列表

你需要做的

  • 环境配置

    • Python 3.x
    • fiddler 一枚(配置抓取手机请求)
    • PyCharm
  • token/session 替换

    • 替换成你们项目对应的 token 等
    • 修改配置文件
    • 修改 response body json 判断逻辑
  • 替换 fiddler js

    • 项目根目录的 fiddler js 整个文件内容替换 fiddler 的 js
    • 打开 fiddler 的 Customize Rules 功能
    • 删除所有内容,并把 fiddler js 内容全部拷贝进去
    • 修改拦截的 host 等信息
    • fiddler 保存请求

fiddler js 自定义信息


//自定义参数设置
public static var filterUrl = "a-webapi.test.b.com";
public static var filePath = "D:\\Fiddler Sessions\\Api\\";
public static var filePathForRequested = "D:\\Fiddler Sessions\\Requested.txt";
public static var filePathForErrorResponse = "D:\\Fiddler Sessions\\ErrorResponse.txt";
public static var filePathForVerifyRequset = "D:\\Fiddler Sessions\\VerifyRequset.txt";
public static var filePathForRemoveSession = "D:\\Fiddler Sessions\\RemoveSession.txt";
public static var filePathForAddSession = "D:\\Fiddler Sessions\\AddSession.txt";
  • github 拉取代码
  • 欢迎一起交流

GitHub

框架地址

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 88 条回复 时间 点赞

够大,先 mark

Heyniu #95 · 2016年08月08日 Author

#1 楼 @sycing 谢谢支持😂

Heyniu #94 · 2016年08月08日 Author

#2 楼 @darker50 谢谢支持,欢迎提供建设性意见😂

好东西,先收藏了在说!!!

Heyniu #92 · 2016年08月08日 Author

#5 楼 @qg__gq 谢谢支持😂

匿名 #91 · 2016年08月08日

碉炸天!GetApi 少了文件扩展名?

Heyniu #10 · 2016年08月08日 Author

#7 楼 @link1220 舞草 这都被发现了,不过未影响使用,你加上吧,我更新一下

思寒_seveniruby 将本帖设为了精华贴 08月08日 15:57

加精理由: 提出了一整套相对完整并符合持续集成的接口测试框架. 并无私的开源. 部分设计非常有亮点. 符合我们 TesterHome 的技术风格. 期待楼主以后逐步放出系列的教程科普.

Heyniu #87 · 2016年08月08日 Author

#10 楼 @seveniruby 谢谢支持,希望 TesterHome 越来越好👍

感谢分享,真心很强大啊

Heyniu #13 · 2016年08月08日 Author

#12 楼 @monkey 共勉,目前只是简单快速的反应接口问题,下一步会更精准 针对性一些,不用每个接口都写用例

作者来点非 http 接口的,譬如 hessian,soap 等其他协议的 O(∩_∩) O~

Heyniu #15 · 2016年08月09日 Author

#14 楼 @kasi 还没接触过,不懂😂

额~~想确认下这个框架干的就是将 python cilent 和 fiddler 抓取的内容进行 diff?

Heyniu #19 · 2016年08月09日 Author

#16 楼 @taflo
你看文章中的最新框架图吧,它做图里的事情

TXT 里的结构是什么样的?

Heyniu #79 · 2016年08月09日 Author

#18 楼 @kofalex 你看下本文章的接口片段就知道了,包含以下信息
request url
request header
request body
response code
response body

这个 TXT 是自己写的 还是录制的? 怎么录制?

Heyniu #21 · 2016年08月09日 Author

#20 楼 @kofalex 是录制的,你可以看我的 github 拉取源码(在文章末尾,喜欢的话请 star 哦)
然后看一下https://testerhome.com/topics/5481 fiddler 保存会话
然后有兴趣可以看一下接口测试框架的一系列文章,本文章开头有贴地址

加个 QQ 聊聊吧 我也在弄接口正好 179461035

的确高大上~只是考虑了正常情况,没有考虑到接口缺参数,参数的类型错误问题?

Heyniu #24 · 2016年08月09日 Author

#23 楼 @lose 这个初衷是接口回放,就是上线前回归接口的,你说的这个功能还没做哦,后面考虑加上

—— 来自 TesterHome 官方 安卓客户端

Heyniu #73 · 2016年08月09日 Author

#22 楼 @kofalex 明天我加你,现在下班了😂

—— 来自 TesterHome 官方 安卓客户端

#17 楼 @heyniu 😅 哎~~~算了白问了

我想问下,1000 个接口是一个个的请求后,然后通过 fiddler 保存下来的么?

Heyniu #69 · 2016年08月10日 Author

#27 楼 @jaychang1989 是的,就是你测试的时候 接口就自动保存下来了,也可以 monkey 的时候跑

#29 楼 @heyniu 比如接口返回的数据多了一条,这个时候你脚本怎么处理的?

Heyniu #32 · 2016年08月10日 Author

#30 楼 @jaychang1989 如果接口是错误的,可以右键这个请求,移除该接口

#31 楼 @heyniu 我没说清楚,是同一个接口,比如用户列表接口,现在新增了一个用户之后,返回的数据多了一条,我看你上面写的好像是与本地存的数据进行对比,这种情况,应该是会出现结果不匹配,这种情况你怎么处理?

Heyniu #33 · 2016年08月10日 Author

#32 楼 @jaychang1989 fiddler 只是录制接口哦,如果录制的接口是错误的,那么保存的也是错误的(需要移除),字段校验是在接口回归的时候验证(跑接口的时候),现在这个功能还在开发中,目前只是校验接口异常,好比移动端的崩溃

可以拿来试试怎么用么

Heyniu #63 · 2016年08月10日 Author

#34 楼 @mads 可以,操作方法看文章中的,你需要做的

#35 楼 @heyniu ok,感觉高大上

Heyniu #38 · 2016年08月10日 Author

#36 楼 @mads 欢迎一起交流,目前还是比较简单的,类似 monkey 发现崩溃问题一样,只能发现严重问题,后续加上重试机制、更详细的字段校验、考虑从接口回放做成接口自动化

有没有 2.7 版本的

Heyniu #39 · 2016年08月11日 Author

#38 楼 @cloudwind 没有哦,你看着转换一下格式就好了

Heyniu [该话题已被删除] 中提及了此贴 08月15日 09:48

为什么要从页面上那接口,不懂啊

Heyniu #56 · 2016年08月17日 Author

#41 楼 @NsirNixp 你能描述清楚点吗,没太懂你意思

感谢楼主的无私奉献,对 Python 不熟,有时间拜读下代码.

#42 楼 @heyniu 获取 api 的目录,我看了一下,你是从页面是拿接口的,
,但是页面上怎么会拿到接口 url 呢

Heyniu #47 · 2016年08月17日 Author

#45 楼 @NsirNixp 这里不是有一个正则匹配吗,你可以学习了 python 爬虫知识,回过头看一下这个你就明白了

#46 楼 @heyniu 不是,我是说前端的接口信息应该是配置在 js 的 ajax 里调用,静态也上你匹配 html 标签里的信息,怎么会有接口信息呢

Heyniu #50 · 2016年08月17日 Author

#47 楼 @NsirNixp 这是一个接口文档的 url,里面包含全部 url

Heyniu #50 · 2016年08月17日 Author

#49 楼 @NsirNixp 其实你不必纠结这些东西,你只需要这是去拿接口列表的就好

#50 楼 @heyniu 嗯,晓得了,谢谢

Heyniu [该话题已被删除] 中提及了此贴 08月18日 09:18

感谢分享啊,周末好好参考一下

请问这个框架可以做业务场景多接口测试么,对于业务场景多接口的测试有什么思路没?

来了 TesterHome 之后,发现每位分享者的接口思路都挺独特的,点赞;

Heyniu #42 · 2016年08月21日 Author

#54 楼 @defias 目前还没有单独的开展接口业务测试,目前框架已经具备这样的能力,需要单独组装接口

虽然有些看不懂,还是感谢分享

匿名 #39 · 2016年08月31日

这个分享很赞,是否可以联系一下做深入交流吗?

Heyniu #61 · 2016年08月31日 Author

#59 楼 @kendydrm 企鹅 335827476

Heyniu #34 · 2016年08月31日 Author

#63 楼 @mads 汉子😅

#64 楼 @heyniu 珍珍是你女朋友吧

只支持 post?

Heyniu #31 · 2016年08月31日 Author

#66 楼 @mads put get 这些方法都一样的,目前我们项目的就是 post,你自己拓展吧

你们没有防重放攻击的机制?

{
    "code": 31,
    "msg": "请勿进行重放攻击"
}

签名认证?像这种
http://developer.dianping.com/app/documentation/signature

碰到这两种东东,感觉有些刺手。

Heyniu #69 · 2016年09月01日 Author

#68 楼 @qi_ling2005 这个肯定是有做的,框架里面的这些信息涉及机密,被我屏蔽了,望谅解

#69 楼 @heyniu 恩哈,如果项目签名、加密方式一变,相关的这块代码就得变了

Heyniu #71 · 2016年09月01日 Author

#70 楼 @qi_ling2005 变的只是内部,对外公开的 api 还是不变的哦

只能先点个赞了。。。

很厉害 学习一下。

Heyniu [该话题已被删除] 中提及了此贴 09月14日 15:36

我的理解,简单的说功能就是比对两个环境的接口差异吧?包括参数和返回字段的对比。然后做基本的数据验证。lz 是这样的么?
设计目的也是为了防止意外引入的大问题吧。

Heyniu #22 · 2016年09月14日 Author

#75 楼 @cyj86
目前验证包括:
response code
json 里面的服务器特有的 code
全字段校验(字段缺少,新增,改变)
全字段类型验证
时间戳的长短验证
后续可以支持自定义的字段验证

先点个赞,我发现现在用 python 的越来越多了

Heyniu #20 · 2016年09月21日 Author

#77 楼 @linaipeng 优势挺多啊

楼主看到你的文章,清理数据这块有 2 个问题
1、如果清理接口本身就失败
2、部分场景只存在创建,没有删除接口
你是如何处理的?

Heyniu #18 · 2016年10月09日 Author

#79 楼 @harmo
1.清理接口你指的是清理创建数据的接口吗,如果不能确保是否成功就需要通过数据库验证了
2.创建数据对应线上日常监控,如果没有删除接口,创建的数据可能就影响线上了,一是考虑排除,不创建;二是通过操作数据库删除这个创建的数据

楼主想问下你做这个框架是为了接口的回归测试吗?

Heyniu #82 · 2016年10月17日 Author

#81 楼 @justcby 目前是,之后会加上接口业务测试。压力测试那些

Heyniu [该话题已被删除] 中提及了此贴 11月01日 09:55

能介绍下 最后一句话 整个过程贯穿 jenkins 执行吗?具体的步骤是怎样?(公司要开始搞接口测试,想多了解一下)🙏 🙏

结合公司具体接口想了一下,如果要确保接口有数据返回,进行全字段验证请问请问是怎么实现的?参数控制是怎么做的?

Heyniu #11 · 2016年11月25日 Author

#86 楼 @liuhao121 就是拿到本次接口返回的所有字段(遍历 json)与上次接口返回的字段作对比,查看有什么变化(新增/删除/修改字段)
jenkins 只需一条命令就可以启动了,前提项目当模块安装在 python 上

这个可以


替换 fiddler 的 JS 后,报这个错,请问是哪里有问题吗?

96楼 已删除

#91 楼 @heyniu 我这边没有这个目录哦,还有请问下 不能自动保存各接口 txt 需要注意哪里的配置呢?谢谢

Heyniu 接口业务测试 -- Java 版 中提及了此贴 06月01日 11:52

楼主的设计思想是,读取 fiddler 录制下来的接口,跟本地数据或者跟上一次录制下来的接口的字段信息进行对比,从而检验接口的正确性,是吗?那为啥不直接跟开发要一份接口文档呢,这样的话就可以直接对接口做自动化测试了,然后再集成到 Jenkins 上做日常监控~我看你写的代码有些复杂,花了半天时间去看了下代码,代码基本是做处理录制下来的接口信息,读写文件,操作文件夹,处理 json 字段等,有些方法的思路还是不错的,但是我个人觉得用这样的方式去测试接口回归接口不算是一个高效的方式~

不知道自己有没有理解错误,也请多多指点~

Ningxw 关于接口自动化的那些事 - 基于 Python 中提及了此贴 07月12日 14:45

感谢楼主!

github 地址怎么是 404 啊

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