先说结论。
覆盖接口:300个。
覆盖模块:12个,其中二级模块47个。
开发模式下,调用接口次数:6828次,脚本运行耗时1h18min3s。
上线模式下,调用接口次数:1257次,脚本运行耗时15min46s。
该系统已稳定运行,本次测试过程中,发现了4个隐藏bug。
此次实践是基于前面分享的几篇博文进行的。
Name | Content |
---|---|
测试框架 | 接口自动化测试框架-AIM |
测试思想 | 结对测试vs随机测试parewise算法性能优化(用例设计层面) |
接口工具 | F12开发者工具,Postman,Fiddler |
编码的难点在于如何总结出各模块各接口的规律,
以尽量简化的代码,尽可能地覆盖更多的场景。
举个例子,下面这两段代码,就是在摸索出代码参数规律后写出来的。
eg1:
代码语言:javascript复制 productList = [("1", "3", sample(["5", "6", "7", "8"], 2)),
("1", "4", ["9", "10"]),
("1", "5", sample(["11", "12", "13"], 2)),
("1", "6", ["14", "15"]),
("2", "3", sample(["5", "6", "7", "8"], 2)),
("2", "4", ["9", "10"]),
("2", "7", ["16", "17"])]
for product in productList:
for monitorLevel in ("1", "2", "3"):
self.req({
"url": full_url("xxx"),
"body": {"xxx": monitorLevel, "xxx": "3", "xxx": product[0],
"xxx": product[1],
"xxx": product[2]}
})
eg2:
代码语言:javascript复制 def test_portrait(self):
# xxx
table = [("1", "xxx"), # xxx
("2", "xxx"), # xxx
("3", "xxx"), # xxx
("4", "xxx"), # xxx
("5", "xxx"), # xxx
("6", "xxx"), # xxx
("7", "xxx"), # xxx
("8", "xxx")] # xxx
data = list()
for t in table:
field = [x['COLUMN_NAME'] for x in
dao.select_dict('''select * from information_schema.COLUMNS t where table_name='%s';''' % t[1])
if x['COLUMN_NAME'] not in ['xxx', 'xxx']]
data.append({"xxx": t[0], "field_name": field})
self.req({
"url": full_url("xxx"),
"body": {"xxx": pub.xxx(1)[0], "data": data}
})
参数是接口测试最核心的部分,怎么处理就决定了接口测试的效率。
1、单选/多选
单选
代码语言:javascript复制"quarter": ("3", "4", "5", "6")
多选
代码语言:javascript复制checkbox("1,2,3,4,6,5")
checkbox是对多选项参数进行了处理:
代码语言:javascript复制def checkbox(option, j=None):
"""
:param option:
:param j: 组装参数的方式 list无 str默认',' 可以指定
:return:
"""
if j is not None:
if isinstance(option, str):
option = option.split(j)
return j.join(option), j.join(sample(option, random.randint(1, len(option) - 1)))
if j is None:
if isinstance(option, str):
j = ','
option = option.split(j)
return j.join(option), j.join(sample(option, random.randint(1, len(option) - 1)))
if isinstance(option, list):
return option, sample(option, random.randint(1, len(option) - 1))
兼顾了“1,2,3”和[1,2,3]两种组装参数的方式。
2、相互联动的参数
也就是说参数a的变化联动改变参数b。
一种方式是构建元组List,循环遍历
代码语言:javascript复制tz_zjbd = [(checkbox("xxx"), ""), # xxx
("", checkbox("xxx"))] # xxx
tz_qixian = [(checkbox("1,2,3,4,5,6"), "", ""),
("", "0", "500")] # xxx
relate = [(tz_zjbd[0], tz_qixian[0]),
(tz_zjbd[1], tz_qixian[1])]
pw = list()
for rel in relate:
p = {
"url": None,
"body": {
"content": [
{
"xxx": "2018-01-01",
"xxx": date("2018-01-01", "2019-03-01"),
"xxx": "2018-01-01",
"xxx": date("2018-01-01", "2019-03-01"),
"xxx": checkbox("xxx"),
"xxx": "0",
"xxx": "100",
"xxx": "0",
"xxx": "100",
"xxx": "0",
"xxx": "100",
"xxx": checkbox("1,2"),
"xxx": checkbox("1,2,3,4,5,6,7"),
"xxx": "",
"xxx": rel[1][0],
"xxx": checkbox("xxx"),
"xxx": checkbox("1,2,3,4,5,6"),
"xxx": checkbox("1,2,3"),
"xxx": ','.join(pub.bankName(3)),
"xxx": checkbox("1,2"),
"xxx": "理财",
"xxx": rel[1][1],
"xxx": rel[1][2],
"xxx": ','.join(pub.cityName(5)),
"xxx": rel[0][0],
"xxx": rel[0][1],
"xxx": "1",
"xxx": "1",
"xxx": "1",
"xxx": "1",
"xxx": "1",
"xxx": "1",
"xxx": "",
"xxx": "asc"
}
]
}
}
pw = pw parewise(p['body']["content"][0])
一种方式是拆开分别处理
代码语言:javascript复制p = {
"url": full_url("xxx"),
"body": {
"xxx": checkbox("1,2,3"),
"xxx": checkbox("1,2"), "xxx": checkbox("1,2,3"),
"xxx": checkbox("1,2,3,4,6,5"),
"xxx": checkbox("1,2,3,4,6,5"),
"xxx": date('2018-01-01', '2018-05-01'),
"xxx": date('2018-05-02', '2019-01-01'), "xxx": ("1", "2"),
"xxx": ("area_ch", "all_ch"), "xxx": "", "xxx": ("1", "2")}
}
for x in parewise(p['body']):
p['body'] = x
self.req(p)
area_list = sample(
"""'海南省','安徽省','福建省','江西省','山东省','河南省','湖北省','湖南省','广东省','上海','广西壮族自治区',
'四川省','贵州省','云南省','西藏自治区','陕西省','山西省','甘肃省','青海省','宁夏回族自治区','新疆维吾尔自治区',
'天津','重庆','内蒙古自治区','辽宁省','吉林省','黑龙江省','江苏省','北京','河北省','浙江省'""".split(','),
20)
p = { # 省份
"url": full_url("xxx"),
"body": {
"xxx": checkbox("1,2,3"),
"xxx": checkbox("1,2"), "xxx": checkbox("1,2,3"),
"xxx": checkbox("1,2,3,4,6,5"),
"xxx": checkbox("1,2,3,4,6,5"),
"xxx": date('2018-01-01', '2018-05-01'),
"xxx": date('2018-05-02', '2019-01-01'), "reports_type": ("1", "2"),
"xxx": "province_ch", "xxx": checkbox(area_list, ","), "xxx": ("1", "2")}
}
for x in parewise(p['body']):
p['body'] = x
self.req(p)
视情况怎么方便怎么来。
3、接口依赖
接口的参数需要从另外接口的返回数据获取。
代码语言:javascript复制r = self.req({
"url": full_url("xxx"),
"body": {}
})
for content in r.json()['content']:
for indexList in content['indexList']:
self.req({
"url": full_url("xxx"),
"body": {
"indexCode": indexList['indexCode'],
"searchDate": "year1"
}
})
4、从数据库取测试数据
代码语言:javascript复制 s = dao.select_dict("""SELECT * FROM xxx""")
p = {
"url": full_url(url),
"body": {
"page": 1,
"pageSize": 15,
"xxx": tuple(sample([x['doc_issuing_agency'] for x in s], 2)),
"xxx": tuple(sample([x['doc_name'] for x in s], 2)),
"xxx": tuple(sample([x['post_no'] for x in s], 2)),
"xxx": tuple(sample([date2str(x['publish_date']) for x in s], 2)),
"xxx": tuple(sample([date2str(x['publish_date']) for x in s], 2)),
"xxx": ""
}
}
for x in parewise(p['body']):
p['body'] = x
self.req(p)
5、请求超时的问题,可以参阅这篇频繁请求报requests异常的处理
6、判断flag
最初的方案是每个接口返回flag不一样,在每个接口返回后,在用例中通过将flag和响应,传参给封装函数做判断。
最新的方案是综合所有接口返回结果,在req函数统一判断。用例中不再判断。
代码语言:javascript复制 def checkFlag(self, p, r):
"""预期,实际"""
err = str([p['url'], p['body'], r.text])
try:
b = False
if (r.json()['flag'] in [1, '1', '', None, 'statistic_by_result', 0,
"0", 'struct_product', 'v_select_jz_single']
or r.json()['message'] in ("暂无数据", "未查询到数据")):
b = True
self.assertEqual(True, b, msg=err)
except (json.JSONDecodeError, KeyError): # 1.返回的不是json,比如下载、404 2.无flag
self.assertEqual(200, r.status_code, msg=err)
说一下心得体会。
做demo和做真实项目完全是两回事。
快捷键是真好用,尤其是代码写多了之后。(虽然写的还不多,但快捷键是真滴)
性能很重要,该优化的要想办法及时优化,哪怕修改实现方式。
码代码前,就算封装一个函数,也要多思考设计,想清楚了再行动,不然挨个挨个重新改一遍真的五味杂陈。
编码效率>框架体验。这是加了用例执行进度1/100这种又去掉之后的感悟。没有必要纠结可有可无的细小体验添加冗余代码。
版权申明:本文为博主原创文章,转载请保留原文链接及作者。