我为Locust加了个 生成测试报告 功能

发布于 2021-05-10 01:10 ,所属分类:软件测试工程师学习资料



Locust是一个轻量级的性能测试工具,和大名鼎鼎的JMeter相比,没有那么大而全的功能,但针对一些简单的压测场景,Locust无疑是个好选择。


本文并非深入教学帖,所以下面只通过一个最简单的场景为例,来大致介绍一下它。


使用说明


假设咱现在要测试一个站点 http://test.valval.cool,测试目标为其中两个接口的性能,分别为GET/api1和POST/api2。


首先安装:

$ pip install locust


接着编写python脚本如下,可以命名为locustfile.py:

from locust import HttpUser, task, between
class WebsiteUser(HttpUser): host = "http://test.valval.cool" wait_time = between(0, 0)
@task def api1(self): self.client.get("/api1")
@task def api2(self): self.client.post("/api2", data={'key': 'value'})

(左右滑动查看完整代码)


启动脚本:

$ locust -f locustfile.py


打开浏览器,访问 http://localhost:8089,可以看到一个还算友好的WebUI。


在这里设定本次测试的并发数(最多有几个模拟用户一起发请求)和孵化率(每秒新增几个用户),点击下面的开始按钮就正式启动压测了。


这里要提一点,对比JMeter这类使用多线程方式来实现并发的传统模式,Locust则使用gevent通过协程的方式模拟并发用户,资源的消耗非常之少。


启动后,默认看到的是Statistics页面,展示内容为当前测试的一些性能指标。


这些指标并不是十分丰富,主要就反映了接口的响应时间和RPS吞吐量这两方面。但我觉得这正是Locust的魅力:简单、够用。


我们平常大部分的压测其实就是这样简简单单的 “拧螺丝” 场景,并不是人人都需要 “造火箭”。


我们在页面上探索一下,点进Charts页面:哇,不错!


这里还提供了3个很棒的图表曲线,可以观察整个测试过程中采样数据的动态变化。


好了就夸到这里,下面来说说Locust让我抓狂的地方。


测试报告之难


如果你安装的是1.2之前的版本,你会发现测试完成后,想要优雅地获取一份测试报告是很艰难的。


Download Data页面仅提供了几个可读性很差的csv文件下载,并且仅针对整个测试的统计结果,无法体现过程中采样数据的变化趋势,所以我们必须再将 Charts 里的3个图表挨个下载下来。


经过一番折腾,最终你得到了下面这样的3个csv和3张图片,不仅操作繁琐,且这些零散的东西可读性非常差。


更可怕的是,因为历史数据仅保存在浏览器的页面里,reload以后之前的数据就会消失,所以如果在测试过程不小心关闭或刷新了一下网页,那么图表就会像下面这样被无情清空...


我太难了!只是想要一份适合人类阅读的报告,咋就这么复杂!


自己开发生成报告


算了,自己动手,丰衣足食,我们一起尝试为 loucst 添加一个友好的生成报告功能吧。


先下载源代码,这里以1.1.1版本代码为例:
$ git clone https://github.com/locustio/locust$ git chekcout 1.1.1
(左右滑动查看完整代码)


大致浏览一下项目结构,把可能涉及的几个重要文件和目录标注了出来,如下:

locust|-- test   // 测试用例在这里|-- user|-- rpc|-- util|-- contrib|-- static  // WebUI 所用到的静态文件目录|   |-- img|   |-- chart.js|   |-- locust.js  // 页面上比较主要的 js 逻辑都写在这里|   |-- style.css|   `-- ...|-- templates  // WebUI 模板文件目录|   |-- index.html  // 目前只有这一个页面|-- __init__.py|-- __main__.py|-- main.py  // locust 启动主入口|-- stats.py  // 统计指标相关的逻辑 大部分在这里|-- web.py  // 使用 Flask 创建了一个 web app|-- argument_parser.py|-- clients.py|-- env.py|-- event.py|-- exception.py|-- log.py|-- runners.py`-- shape.py

(左右滑动查看完整代码)


来理一下思路:期望生成的报告内容目前WebUI上都已有提供,也就是那3个csv和3张图表,所以我们需要分析一下web.py,将这些要呈现的数据集中到一个view中,然后增加一个页面,把上述内容一并渲染展示即可。


看看那3个csv的数据是从哪来的,直接在页面上就可以查到对应的链接,url分别为/stats/requests/csv、/stats/failures/csv、/exceptions/csv 如下:


在web.py中找到了/stats/requests/csv对应的view是request_stats_csv,如下:

@app.route("/stats/requests/csv")@self.auth_required_if_enableddef request_stats_csv():    data = StringIO()    writer = csv.writer(data)    requests_csv(self.environment.runner.stats, writer)    response = make_response(data.getvalue())    file_name = "requests_{0}.csv".format(time())    disposition = "attachment;filename={0}".format(file_name)    response.headers["Content-type"] = "text/csv"    response.headers["Content-disposition"] = disposition    return response

(左右滑动查看完整代码)


可以看出,最重要的是requests_csv(self.environment.runner.stats, writer)这句,目标数据来源于stats。紧接着跟进requests_csv函数,会发现响应时间和吞吐量等数据对应stats中的变量名分别为median_response_time、avg_response_time、max_response_time、total_rps等。


另外两个view分别是failures_stats_csv和 exceptions_csv,分析方法类似就不赘述了。


结合上面对源码的探究,我们可以新建一个类似下面这样的view,将上文中提到的3个csv中的内容集中在一起。

@app.route("/stats/report")@self.auth_required_if_enableddef stats_report():    stats = self.environment.runner.stats    requests_statistics = list(chain(sort_stats(stats.entries), [stats.total]))    failures_statistics = sort_stats(stats.errors)    exceptions_statistics = []    for exc in environment.runner.exceptions.values():        exc['nodes'] = ", ".join(exc["nodes"])        exceptions_statistics.append(exc)    return render_template('report.html', **locals())

(左右滑动查看完整代码)


UI 方面,在templates目录下新建一个名为report.html的模板文件,在其中渲染requests_statistics、failures_statistics、exceptions_statistics为表格形式即可。效果如下:


接下来,还有3张图表要展示到报告中。咱先研究下Charts页面里的那几个图怎么来的。


查看index.html源码发现,图表使用echarts生成,浏览器每隔2秒会发一次ajax请求,获取实时的采样数据后push到图表数据中。


整个过程如下,节选自locust.js文件:

var rpsChart = new LocustLineChart($(".charts-container"), "Total Requests per Second", ["RPS", "Failures/s"], "reqs/s", ['#00ca5a', '#ff6d6d']);var responseTimeChart = new LocustLineChart($(".charts-container"), "Response Times (ms)", ["Median Response Time", "95% percentile"], "ms");var usersChart = new LocustLineChart($(".charts-container"), "Number of Users", ["Users"], "users");
function updateStats() { $.get('./stats/requests', function (report) { window.report = report;
renderTable(report); renderWorkerTable(report);
if (report.state !== "stopped"){ // get total stats row var total = report.stats[report.stats.length-1]; // update charts rpsChart.addValue([total.current_rps, total.current_fail_per_sec]); responseTimeChart.addValue([report.current_response_time_percentile_50, report.current_response_time_percentile_95]); usersChart.addValue([report.user_count]); } else { appearStopped(); }
setTimeout(updateStats, 2000); });}updateStats();

(左右滑动查看完整代码)


这也就是刷新页面后图表就被清空的原因了!我们的测试报告可不能基于前端页面保存的数据啊,那太不可靠了。


这要怎么做呢?暂时没有思路,我们继续研究一下源码,看看有没有什么启发。


看到main.py中有这么一段,十分激动:

if options.csv_prefix:gevent.spawn(stats_writer,environment,options.csv_prefix,full_history=options.stats_history_enabled).link_exception(greenlet_exception_handler)

(左右滑动查看完整代码)


在启动Locust时如果带上了--csv和--csv-full-history参数的话,就会触发这段,将启动一个协程来执行stats_writer函数。


跟进stats_writer会发现,他的功能是定时将当前实时的采样数据写入本地的csv文件中这个函数和我们要做的事情很像。


那我们就仿照它再写一个类似的协程,定时将采样数据保存到服务端内存中即可。代码类似这样:

def stats_history(runner):    """Save current stats info to history for charts of report."""    while True:        stats = runner.stats        r = {            'time': datetime.datetime.now().strftime("%H:%M:%S"),            'current_rps': stats.total.current_rps or 0,            'current_fail_per_sec': stats.total.current_fail_per_sec or 0,            'response_time_percentile_95': stats.total.get_current_response_time_percentile(0.95) or 0,            'response_time_percentile_50': stats.total.get_current_response_time_percentile(0.5) or 0,            'user_count': runner.user_count or 0,        }        stats.history.append(r)        gevent.sleep(HISTORY_STATS_INTERVAL_SEC)

(左右滑动查看完整代码)


最后,在Download Data页面加上个生成报告的链接,大功告成!来看看完整的效果。


结语


阅读完此篇,希望你能有如下收获:


如果你从未了解过 Locust,希望此文为你打开了一扇窗,可以在今后尝试使用它,欲了解深入的高级使用技巧可进一步查阅网络上大量的现成文章。


如果你曾使用过 Locust,也同样有为生成测试报告而发愁的经历,那么现在你的困扰得以解决了。


如果你是开发爱好者,希望本文带着你手拉手一起体验了,从发现问题到思考分析,再到通过编码去解决问题的全过程。


篇幅有限,本文仅展示了部分比较典型的代码片段,想表达的重点也不是如何写代码,而是思考分析的过程。具体可以通过Github查看这个PR:https://github.com/locustio/locust/pull/1516/files,了解完整的代码。



End

链接:https://www.jianshu.com/p/50f65e72af73

本文为51Testing经授权转载,转载文章所包含的文字来源于作者。如因内容或版权等问题,请联系51Testing进行删除

推荐阅读

点击阅读☞两种常用Selenium测试框架的对比

点击阅读☞Selenium 常用函数总结

点击阅读☞Python+Selenium,实现12306模拟登录

点击阅读☞Python+Selenium,实现贴吧自动发帖

点击阅读☞Selenium破解滑动验证码

“”一起来充电吧!

相关资源