最近发现公司很多是jmeter进行的自动化测试,存在的问题:原生报告巨丑无比,并且还没有那么友好的展示详情。
话不多说,开搞。
首先是jmeter配置的调整,需要拿到整个请求的详情
1、找到jmeter bin目录下的jmeter.properties修改配置如下
2、找到jmeter bin目录下的修改user.properties添加配置
jmeter.save.saveservice.output_format=xml
jmeter.save.saveservice.response_data=true
jmeter.save.saveservice.samplerData=true
jmeter.save.saveservice.requestHeaders=true
jmeter.save.saveservice.url=true
jmeter.save.saveservice.responseHeaders=true
3、通过/u01/jmeter/apache-jmeter-5.3/bin/jmeter.sh -n -t xxx.jmx -l result.xml -j test.log 输出报告
这个时候就会发现输出的xml报告包含了用例的层级和详情信息
<httpSample t="1" lt="1" ts="1450684950333" s="true" lb="app.testdelay" rc="200" rm="OK" tn="appdelay-3000g3m 1-1" dt="" by="2265"/>
t表示从请求开始到响应结束的时间
lt表示整个的空闲时间
ts表示访问的时刻
s表示返回的结果true表示成功,false表示失败
lb表示标题
rc表示返回的响应码
rm表示响应信息
tn表示线程的名字“1-138”表示第1个线程组的第138个线程。
dt表示响应的文件类型
by表示请求和响应的字节数
到这里差不多完成一半了,有这个xml,我把它解析成自己想要的pytest allure不就行了
4、把xml报告处理成pytest,python3代码如下
#通过命令行来执行 ,例如python3 dealReportXml.py result.xml test_demo.py report/resource
# -*- coding: utf-8 -*-
# @Time : 2020/11/30 4:29 下午
import xml.etree.cElementTreeas ET
import json,os,uuid
import sys
# xmlObject xml工程
# checkString 这个其实是固定值 httpSample,只不过后面想要获取其他结构参数化
# num 当时想嵌套多层,后来发现allure只三层结构,其实这个也没啥用
# result 传递解析的xml
# demoFile 生成的pytes文件
# featureIndex 用来排序参数用例,按照jmeter中的树结果
# storyIndex 同上,是第二层级的排序
def checkChildren(xmlObject, checkString, num, result, demoFile, featureIndex, storyIndex):
for childrenin xmlObject:
try:
if num == 1 and children.attrib['sby'] != "0":
featureIndexStr='#'+str(featureIndex)+" " if featureIndex >= 10 else '#0'+str(featureIndex)+" "
result["feature"] = featureIndexStr+children.attrib['lb']
featureIndex += 1
if num >= 2 and children.tag== "sample":
storyIndexStr= '#' + str(storyIndex) + " " if storyIndex >= 10 else '#0' + str(
storyIndex) + " "
result["story"] = storyIndexStr+children.attrib['lb']
storyIndex += 1
except:
pass
if children.tag== checkString:
result['case_name'] = children.attrib['lb']
for httpSampleChildrenin children:
result[httpSampleChildren.tag] = httpSampleChildren.text
if httpSampleChildren.tag== 'assertionResult':
for assertionResultChildrenin httpSampleChildren:
result[assertionResultChildren.tag] = assertionResultChildren.text
feature= result['feature'] if "feature" in result else None
story= result['story'] if 'story' in result else None
case_name= result['case_name'] if 'case_name' in result else None
URL= result['java.net.URL'] if 'java.net.URL' in result else None
method= result['method'] if 'method' in result else None
requestHeader= result['requestHeader'] if 'requestHeader' in result else None
queryString= result['queryString'] if 'queryString' in result else None
responseData= result['responseData'] if 'responseData' in result else None
failureMessage= result['failureMessage'] if 'failureMessage' in result else None
failure= result['failure'] if 'failure' in result else "false"
print(feature,story,case_name,failureMessage,URL)
storyString= "@allure.story('"+result['story']+"') # 二级目录" if 'story' in result else ''
pyString= '''
@allure.feature('{feature}') # 一级目录{story}
@allure.title("{case_name}")
def test_allure_report_{num}():
with allure.step('请求url:{URL}'):
print('请求url:{URL}')
with allure.step('请求方法:{method}' ):
print('请求方法:{method}' )
with allure.step('请求头:{requestHeader}' ):
print('请求头:{requestHeader}' )
with allure.step(\'''请求数据:{queryString}\'''):
print(\'''请求数据:{queryString}\''')
with allure.step('接口返回:{responseData}'):
print('接口返回:{responseData}')
with allure.step('断言结果:{failureMessage}'):
print('断言结果:{failureMessage}')
assert "{failure}" == 'false' '''.format(feature=feature,story=storyString,case_name=case_name,
num=str(uuid.uuid1()).replace('-',''),
URL=URL,method=method, requestHeader=str(requestHeader).replace('\n', '').replace('\r', ''),
queryString=str(json.dumps(queryString)).replace("'","\""),
responseData=str(json.dumps(responseData)).replace("'","\""),
failureMessage=str(failureMessage).replace("'","\""),failure=failure)
print(pyString)
with open(demoFile,'a') as c:
c.write(pyString)
else:
checkChildren(children,checkString,num+1,result,demoFile,featureIndex,storyIndex)
if __name__== '__main__':
#通过命令行来执行 ,例如python3 dealReportXml.py result.xml test_demo.py report/resource
commonndLines= sys.argv
print(commonndLines)
with open(commonndLines[2], "w") as demo:
demo.write('''
# -*- coding: utf-8 -*-
import allure
''')
tree= ET.parse(commonndLines[1])
root= tree.getroot()
checkChildren(root,"httpSample",1,{},commonndLines[2],1,1)
pyString= 'pytest --capture=no '+commonndLines[2]+' --alluredir '+commonndLines[3]
os.system(pyString)
alSring= "allure generate "+commonndLines[3]+" -o report/html --clean"
os.system(alSring)
5、打开生成的报告
细心的你会发现,这里面的时间是错误的,别急,我们来处理一下。
回头看看jmeter报告中的协议
t表示从请求开始到响应结束的时间
lt表示整个的空闲时间
ts表示访问的时刻
s表示返回的结果true表示成功,false表示失败
lb表示标题
rc表示返回的响应码
rm表示响应信息
tn表示线程的名字“1-138”表示第1个线程组的第138个线程。
dt表示响应的文件类型
by表示请求和响应的字节数
只要我们把对应的时间替换报告中对应的时间即可
6、优化 把jmeter的实际执行时间匹配进去,最终版本,如下
直接执行命令
python3 dealReportXml.py result.xml test_demo.py report/resource
allure generate report/resource -o report/html --clean
完整代码如下
# -*- coding: utf-8 -*-
# @Time : 2020/11/30 4:29 下午
import xml.etree.cElementTreeas ET
import json,os,uuid
import sys
from osimport listdir
from os.pathimport isfile, join
# xmlObject xml工程
# checkString 这个其实是固定值 httpSample,只不过后面想要获取其他结构参数化
# num 当时想嵌套多层,后来发现allure只三层结构,其实这个也没啥用
# result 传递解析的xml
# demoFile 生成的pytes文件
# featureIndex 用来排序参数用例,按照jmeter中的树结果
# storyIndex 同上,是第二层级的排序
# timeChekout 用来替换allure报告中的时间为jmeter报告时间
def checkChildren(xmlObject, checkString, num, result, demoFile, featureIndex, storyIndex,timeChekout):
for childrenin xmlObject:
try:
if num == 1 and children.attrib['sby'] != "0":
featureIndexStr='#'+str(featureIndex)+" " if featureIndex >= 10 else '#0'+str(featureIndex)+" "
result["feature"] = featureIndexStr+children.attrib['lb']
featureIndex += 1
if num >= 2 and children.tag== "sample":
storyIndexStr= '#' + str(storyIndex) + " " if storyIndex >= 10 else '#0' + str(
storyIndex) + " "
result["story"] = storyIndexStr+children.attrib['lb']
storyIndex += 1
except:
pass
if children.tag== checkString:
result['caseName'] = children.attrib['lb']
for httpSampleChildrenin children:
if httpSampleChildren.tag== 'assertionResult':
assertionResultChildrenTag=[]
for assertionResultChildrenin httpSampleChildren:
assertionResultChildrenTag.append(assertionResultChildren.tag)
result[assertionResultChildren.tag] = assertionResultChildren.text
if "failureMessage" not in assertionResultChildrenTag:
result['failureMessage'] = None
else:
result[httpSampleChildren.tag] = httpSampleChildren.text
feature= result['feature'] if "feature" in result else None
caseName= result['caseName'] if 'caseName' in result else None
URL= result['java.net.URL'] if 'java.net.URL' in result else None
method= result['method'] if 'method' in result else None
requestHeader= result['requestHeader'] if 'requestHeader' in result else None
queryString= result['queryString'] if 'queryString' in result else None
responseData= result['responseData'] if 'responseData' in result else None
failureMessage= result['failureMessage'] if 'failureMessage' in result else None
failure= result['failure'] if 'failure' in result else "false"
menthodUuid= str(uuid.uuid1()).replace('-','')
start= int(children.attrib['ts'])
stop= int(children.attrib['ts'])+int(children.attrib['t'])
timeChekout["test_demo#test_allure_report_"+menthodUuid]={"start":start,"stop":stop}
storyString= "@allure.story('"+result['story']+"') # 二级目录" if 'story' in result else ''
pyString= '''
@allure.feature('{feature}') # 一级目录{story}
@allure.title("{caseName}")
def test_allure_report_{num}():
with allure.step('请求url:{URL}'):
print('请求url:{URL}')
with allure.step('请求方法:{method}' ):
print('请求方法:{method}' )
with allure.step('请求头:{requestHeader}' ):
print('请求头:{requestHeader}' )
with allure.step(\'''请求数据:{queryString}\'''):
print(\'''请求数据:{queryString}\''')
with allure.step('接口返回:{responseData}'):
print('接口返回:{responseData}')
with allure.step('断言结果:{failureMessage}'):
print('断言结果:{failureMessage}')
assert "{failure}" == 'false' '''.format(feature=feature,story=storyString,caseName=caseName,
num=menthodUuid,URL=URL,method=method, requestHeader=str(requestHeader).replace('\n', '').replace('\r', ''),
queryString=str(json.dumps(queryString)).replace("'","\""),
responseData=str(json.dumps(responseData)).replace("'","\""),
failureMessage=str(failureMessage).replace("'","\""),failure=failure)
with open(demoFile,'a') as c:
c.write(pyString)
else:
checkChildren(children,checkString,num+1,result,demoFile,featureIndex,storyIndex,timeChekout)
return timeChekout
if __name__== '__main__':
#通过命令行来执行 ,例如python3 dealReportXml.py result.xml test_demo.py report/resource
commonndLines= sys.argv
with open(commonndLines[2], "w") as demo:
demo.write('''
# -*- coding: utf-8 -*-
import allure
''')
tree= ET.parse(commonndLines[1])
root= tree.getroot()
realQueryTime= checkChildren(root,"httpSample",1,{},commonndLines[2],1,1,{})
pyString= 'pytest --capture=no '+commonndLines[2]+' --alluredir '+commonndLines[3]
os.system(pyString)
files= [mfor min listdir(commonndLines[3]+"/") if isfile(join(commonndLines[3]+"/", m))]
for filein files:
if ".json" in file:
try:
with open(commonndLines[3]+"/" + file, 'r') as f:
jsonStr= json.loads(f.read())
start= realQueryTime[jsonStr['fullName']]["start"]
stop= realQueryTime[jsonStr['fullName']]["stop"]
jsonStr['start'] = start
jsonStr['stop'] = stop
with open(commonndLines[3]+"/" + file, "w") as m:
json.dump(jsonStr, m, indent=2, sort_keys=True, ensure_ascii=False) # 写为多行
except:
pass