简单介绍一下背景:
新接了一个测试项目,处理运动的视频流,接口是grpc的,双向流式,对于实时要求较高。
接口发送逻辑:每次请求需要发送一帧数据+参数,根据视频帧率,以25fps为例,每间隔40ms之后,发送下一帧,中间需要根据返回结果,调整入参。响应时间根据不同类型运动类型计算方式也不同。
本人一直都是使用python的,之前接触的项目接口也都是处理很慢,属于非CPU 密集型任务,python 的GIL影响并不大,python编写测试工具完全够用了。但是在这次项目中用python编写工具就不行了,通过验证也确实不能满足要求,尤其是在高并发下。
接下来,要寻求新的解决方案了。。。我直接选中了jmeter,之前接触的比较少,一直没有深入使用,这次刚好趁这次机会,揭开他的面纱。于是开始了下面的探索。
- 探索1:使用jmeter grpc插件 。【pass】
- 原因:因为本身项目接口双向流式,再加上中间需要输入的参数需要根据结果不同。现有的grpc采样器不好搞。
- 探索2:使用BeanShell sampler 调用python脚本【pass】
- 原因:多台测试环境,必须要在本机测试,需要给所有测试机器,准备python环境和各种module。后期操作复杂。也因此想到了 打包二进制可执行文件的方式。
- 探索3:使用os process sampler 。【ok】
- 原因:因为前期已经使用了python,已经有一定的积累,整个测试逻辑已经实现 。os process 能实现服务器脚本执行,可视化参数,要比beanshell拼接命令方式更简单。
首先前期的jmeter环境准备我就不赘述啦。想要用python实现复杂的测试逻辑,又想要jmeter拜托GIL困扰的小伙伴,可以参考。
第一步:使用python实现好复杂的接口测试逻辑。 确定好入参 和 结果(响应时间,响应结果,响应返回码等。结果只能标准输出,后面提取参数覆盖采样器默认值,好方便后期报告生成。
采样器字段可以参考SampleResult (Apache JMeter dist API)
第二步:使用pyinstaller 打包写好的python测试脚本,二进制文件默认在当前目录的dist下。[如果不想打包,可以只用beanshell sampler]
pyinstaller -F PE_GrpcHandle.py
第三步:使用os process sampler。
Command :要执行的操作命令
working direcotry: 表示执行命令的工作目录
command parameters:表示命令入参。在python脚本中使用argv获取。
if __name__ == '__main__':
try:
param = json.loads(sys.argv[1])
except:
print(sys.argv[1])
print("启动命令格式:python3 PE_GrpcHandle paramtesr({key:value})。例如:")
sys.exit(1)
print("本次启动参数:{}".format(param))
//以下是调用业务逻辑的测试方法。。可以忽略
pe_handle = PE_GrpcHandle(host=param["host"], port=param["port"], video_paths=param["video_path"].split("##"),
sport_type=param["sport_type"], mode=param["mode"], facility_id=param["facility_id"],
lanes=param["lanes"], vest=param["vest"], broadcast_index=param["broadcast_index"])
pe_handle.edu_pe_stream()
第四步:使用Regular Expression Extractor 正则表达式,提取结果。
我提取了四个,响应时间,错误码,响应结果,总共的帧数。
打印的结果如下:
响应时间提取正则表达式:self.ave_response_time=([.|\d]+),self.foul_info
第五步:复写sampler result默认参数。使用的JSR223 PostProcessor。
我这里复写了response code,response message,response body 和byte ,大家可以根据需要,不复写都没关系。
byte我给的是当前测试阶段一共发送多少帧,后面可以统计帧的吞吐。
默认的response code 是脚本执行结果,脚本执行出错500或者-2啥的,执行正确0。不涉及脚本内部逻辑,远远不够用。
默认的response message为空
默认的response body是脚本打印的所有文本,我的脚本还有一堆日志,其实只要关键信息就行。
默认的sendbytes为0.
def delta = ((vars.get('ave_response_time') as Float) *1000 as long)
//java.lang.reflect.Field elapsed = prev.getClass().getDeclaredField('elapsedTime')
//elapsed.setAccessible(true)
//elapsed.set(prev, delta)
org.apache.commons.lang3.reflect.FieldUtils.writeField(prev, "elapsedTime", delta, true)
log.info("ResponseCode"+prev.getResponseCode())
log.info(vars.get('framemin'))
if(prev.getResponseCode().equals("0")){
prev.setResponseCode(vars.get('successcode'))
prev.setResponseMessage(vars.get('error_msg'))
prev.setResponseMessage(vars.get('response_body'))
prev.setSentBytes(vars.get('framemin') as long)
}
SampleResult (Apache JMeter dist API) 函数和方法可以看这里。
第六步:增加查看结果树和Summary report
这两个组件太常见了,这个不赘述啦。直接展示
最后总体的看一下我的组件结构: