需求背景
传统测试开发的接口自动化工程模型
一般掌握python的测试开发人员都喜欢使用python创建一套自动化工程。其中,大部分人应该都接触过一套架构模式:
语言:python(2/3)
库:requests,unittest,htmltestrunner
以requests实现http请求,unittest实现断言及报告,htmltestrunner实现html测试报告的生成。这是三大主要核心结构。当然还有比如用mvc框架集自动化执行和测试报告预览的平台,又或者是用邮件库实现的测试报告发送等各种形式。
为什么由传统代码工程改为工具平台
测试的入门门槛是比较低的,可能开发人员必须是精通某一门语言,多种技术。而测试就存在不懂任何语言,不懂任何技术的情况,并且即便是技术流,那也未必恰好都会使用python,可能有些人更喜欢java,或者是其它语言。还有就是每个人都有一定的发展过程,总会有他的技术薄弱的阶段。另外,一个企业专门组织测试开发团队是比较消耗成本的,而自动化开发的工作放在个别人的手上就很难实现同步。所以开发测试平台是一种人人都能参与自动化开发的方式。
我们自动化平台的结构
我们是基于httprunner框架进行本地化改造实现的平台。但是核心功能没有做太多变化,只是做了很多加法。
httpruuner的实现是使用python3的django框架完成的,有web交互页面,mysql数据库。其中,requests和unittest还是他的主要核心。自动化测试用例的实现,要求测试人员掌握http协议,把请求的必要信息填入用例内,加上必要的响应断言,只要做好数据回收,保证用例可以反复执行就好。其中有些需要计算,遍历,筛选,随机等无法写死的参数,则必须使用python函数进行实现,在用例中调用函数即可。所以测试开发的工作在这个时候就是负责写这些工具化函数。
演变1:swagger爬虫
这一部分的实现我以后有机会会分享出来。swagger接口文档是放在源码上的框架,我通过爬取接口文档的api,把它转化成自动化平台用例的数据格式,插入到mysql中,就实现了代替手工去创建case的方式。测试人员只需要把必要的参数维护进去就够了,这样也能避免测试人员手工创建case的时候经常手误犯错。
演变2:fiddler爬虫
实现第一步之后,测试人员需要通过手工操作,根据fiddler的抓包结果去提取数据维护进swagger抓下来的无数据用例内。那么显然现在的主要工作就是找到在fiddler里想要抓的接口,如果是webform或query这种表单,就需要一个字段一个字段的复制,还是有些麻烦的。所以心生一计----对fiddler进行爬虫。
实现思路
fiddler是应用于windows平台一款http协议抓包工具。作为一个中间代理,可以抓到手机或者电脑上发出的http请求,从而进行分析。如果看到这里不知道fiddler是做什么用的,建议去网上搜索一下它的使用方式,这里就不做关于它的分享了。比如我打开fiddler,在要实现自动化的业务上进行一番操作,同时注意抓包结果,你至少要知道哪些是你需要的。这个时候可以删除不需要实现自动化的,重复的接口,只保留想要实现自动化的抓包记录。全选后,右键-sava-selected session-as text.也就是把抓包的结果储存到本地的一个txt文档里。(为了保护我司域名,用张表情遮挡一下host)
保存下来之后见下图:
大概看一下:
1、请求方法
2、请求头内的:授权口令
3、入参
4、响应
这是几个必要参数。响应通常我们校验code是不是200就可以了。所以对于码农来说,就是把这个txt文件想要的东西爬出来做好归类,再以测试平台内数据存储需要的格式塞到库里就OK了。
上菜
def analysisFiddler(headers):
f = open(r"D:\AutoBugrate\AuToBugrate\1616_Full.txt",encoding="utf-8").read()
reqList = f.split("------------------------------------------------------------------")
newReqList = []
for i in reqList:
a = i.split("\n")
while "" in (a):
a.remove("")
if a != []:
newReqList.append(a)
nameList = []
fullUrlList = []
methodList = []
for i in newReqList:
host = i[1].split(": ")[1]
nameList.append(i[0].split(" ")[1].split(host)[1].replace("/","_"))
fullUrlList.append(i[0].split(" ")[1])
methodList.append(i[0].split(" ")[0])
# print(nameList)
# print(fullUrlList)
publicBaseReqList = [{'test':
{'skipIf': {},
'request': {'url': url,
'times': '1',
'sleep': '0',
'method': method,
'headers': headers},
'rollback': {},
'name': name,
'validate': [{'comparator': 'equals',
'check': 'content.code',
'expected': 200}]}}
for url,method,name in zip(fullUrlList,methodList,nameList)] #定义一个通用的request用于sql
# print(publicBaseReqList)
for i,l in zip(newReqList,publicBaseReqList):
i.pop()
for j in i:
for k in (j.split(" ")):
if "{" in k:
l['test']['request']['json'] = eval(k)
# print(publicBaseReqList)
create_time = update_time = dayTime_get()
caseType = '1' #1表示用例 2表示配置
belong_project = '外拓项目'
include = "[{'config': ['2980', '外拓配置-ST']}]"
author='julian'
belong_module_id = '156'
conn = pymysql.connect(
host='192.168.x.xx',
port=3306,
user='root',
password='xxxxxx',
db='autobots',
cursorclass=pymysql.cursors.DictCursor,
charset='utf8'
)
cur = conn.cursor()
for name,request in zip(nameList,publicBaseReqList):
# if (not bool(re.search('[a-zA-Z]',name))) and (name not in popNameList):#v1.1增加
insertCaseSql = '''INSERT INTO `autobots`.`testcaseinfo` (
`id`,`create_time`,`update_time`,
`type`,`name`,`belong_project`,
`include`,`author`,`request`,`belong_module_id`)
VALUES(NULL,"%s","%s","%s","%s","%s","%s","%s","%s","%s")'''%(
create_time,update_time,caseType,name,belong_project,include,author,request,belong_module_id)
print(insertCaseSql)
print('-'*200)
cur.execute(insertCaseSql)
conn.commit()
time.sleep(1)
cur.close()
conn.close()
analysisFiddler(headers={'Authorization': 'Bearer $Authorization'})
大概介绍一下代码思路:
1、首先读取fiddler的文档路径
2、文档内一一排减号作为两次不同接口请求的分割线,所以我们使用它先做一次粗分割
3、然后再利用换行符做进一步的分割,并去掉空行
4、先提取每个请求的第一行,先抽取出host,再以host为分割标记,截取url,把url中的路径标记"/"替换成"-",因为在平台内,用例名字里包含正斜杠会被解析成路径,从而报用例找不到。替换完后,作为用例名字,存好放在列表内。同理,把url提炼出来,把请求方法也提炼出来。
5、下一步publicBaseReqList这个字典,就是测试平台的数据库内存储的测试用例的数据结构。skipif是跳过的条件,这里空着即可。request里的内容是主要区别,包含url,重试次数,睡眠时间,请求方法,请求头。rollback是回滚条件,也空着。name就是用例名称,validate里面的内容就是断言。其中我把请求头放在形参内,因为仅用到一个身份口令,这一批接口是一致的。后面就是做一次for循环,使用抓出来的url,method,name三个列表,把数据分别组装进去。每一个case都有一套数据结构。
6、有了基础的请求结构,还缺最主要的,body。由于我抓的这批接口都是application/json格式,所以我直接用"{"去找到这个json串。为了避免最后面的响应串干扰,先把它pop掉。
7、再往下就是插库了,把数据库内必要的参数补全,比如创建人,创建时间,更新时间,用例所属的工程,模块,以及前提条件(include),这里就包含一些基础的登录操作,提供token等信息。
8、最后就是愉快的执行。
它确实与swagger爬出来的不一样,因为名字都是按照接口路径拼的,swagger的是有中文名字的,不过这个不重要。看看里面
请求:
断言:
总体执行一下:
全部通过。
但是不要高兴的太早,因为我知道我抓的这批接口都只是查询类的。如果有一些增删改之类的,应该需要做一些微调,局部参数化。但是这已经很节省时间了对不对,因为脚本不是只使用一次的。后续还会做进一步的优化,让它适应各种各样的类型。
比如请求头更丰富多变,用什么样的方法过滤form,query的请求格式,都需要兼容。
不过,掌握http协议,掌握python爬虫和正则,自然心中有码,逢山开路遇水搭桥呗。