HttpRunner ApiTestEngine (2) 探索优雅的测试用例描述方式

debugtalk · 2017年07月07日 · 最后由 yjq 回复于 2018年06月19日 · 6820 次阅读
本帖已被设为精华帖!

《ApiTestEngine 演进之路(1)搭建基础框架》一文中,我们完成了ApiTestEngine基础框架的搭建,并实现了简单接口的测试功能。

接下来,我们就针对复杂类型的接口(例如包含签名校验等机制),通过对接口的业务参数和技术细节进行分离,实现简洁优雅的接口测试用例描述。

传统的测试用例编写方式

对于在自动化测试中将测试数据代码实现进行分离的好处,我之前已经讲过多次,这里不再重复。

测试数据与代码实现分离后,简单的接口还好,测试用例编写不会有什么问题;但是当面对复杂一点的接口(例如包含签名校验等机制)时,我们编写自动化测试用例还是会比较繁琐。

我们从一个最常见的案例入手,看下编写自动化测试用例的过程,相信大家看完后就会对上面那段话有很深的感受。

以 API 接口服务(Mock Server)的创建新用户功能为例,该接口描述如下:

请求数据:
Url: http://127.0.0.1:5000/api/users/1000
Method: POST
Headers: {"content-type": "application/json", "Random": "A2dEx", "Authorization": "47f135c33e858f2e3f55156ae9f78ee1"}
Body: {"name": "user1", "password": "123456"}
######
预期的正常响应数据:
Status_Code: 201
Headers: {'Date': 'Fri, 23 Jun 2017 07:05:41 GMT', 'Content-Length': '54', 'Content-Type': 'application/json', 'Server': 'Werkzeug/0.12.2 Python/2.7.13'}
Body: {"msg": "user created successfully.", "success": true, "uuid": "JsdfwerL"}

其中,请求Headers中的Random字段是一个 5 位长的随机字符串,Authorization字段是一个签名值,签名方式为TOKEN+RequestBody+Random拼接字符串的MD5值。更具体的,RequestBody要求字典的Key值按照由小到大的排序方式。接口请求成功后,返回的是一个JSON结构,里面的success字段标识请求成功与否的状态,如果成功,uuid字段标识新创建用户的唯一 ID。

相信只要是接触过接口测试的同学对此应该都会很熟悉,这也是后台系统普遍采用的签名校验方式。在具体的系统中,可能字符串拼接方式或签名算法存在差异,但是模式基本上都是类似的。

那么面对这样一个接口,我们会怎样编写接口测试用例呢?

首先,请求的数据是要有的,我们会先准备一个可用的账号,例如{"password": "123456", "name": "user1"}

然后,由于接口存在签名校验机制,因此我们除了要知道服务器端使用的 TOKEN(假设为debugtalk)外,还要准备好Random字段和Authorization字段。Random字段好说,我们随便生成一个,例如A2dExAuthorization字段就会复杂不少,需要我们按照规定先将RequestBody根据字典的Key值进行排序,得到{"name": "user1", "password": "123456"},然后与TOKENRandom字段拼接字符串得到debugtalk{"password": "123456", "name": "user1"}A2dEx,接着再找一个MD5工具,计算得到签名值a83de0ff8d2e896dbd8efb81ba14e17d

最后,我们才可以完成测试用例的编写。假如我们采用YAML编写测试用例,那么用例写好后应该就是如下样子。

-
    name: create user which does not exist
    request:
        url: http://127.0.0.1:5000/api/users/1000
        method: POST
        headers:
            Content-Type: application/json
            authorization: a83de0ff8d2e896dbd8efb81ba14e17d
            random: A2dEx
    data:
        name: user1
        password: 123456
    response:
        status_code: 201
        headers:
            Content-Type: application/json
        body:
            success: true
            msg: user created successfully.
            uuid: JsdfwerL

该测试用例可以在ApiTestEngine中正常运行,我们也可以采用同样的方式,对系统的所有接口编写测试用例,以此实现项目的接口自动化测试覆盖。

但问题在于,每个接口通常会对应多条测试用例,差异只是在于请求的数据会略有不同,而测试用例量越大,我们人工去准备测试数据的工作量也就越大。更令人抓狂的是,我们的系统接口不是一直不变的,有时候会根据业务需求的变化进行一些调整,相应地,我们的测试数据也需要进行同步更新,这样一来,所有相关的测试用例数据就又得重新计算一遍(任意字段数据产生变化,签名值就会大不相同)。

可以看出,如果是采用这种方式编写维护接口测试用例,人力和时间成本都会非常高,最终的结果必然是接口自动化测试难以在实际项目中得以开展。

理想的用例描述方式

在上面案例中,编写接口测试用例时之所以会很繁琐,主要是因为接口存在签名校验机制,导致我们在准备测试数据时耗费了太多时间在这上面。

然而,对于测试人员来说,接口的业务功能才是需要关注的,至于接口采用什么签名校验机制这类技术细节,的确不应耗费过多时间和精力。所以,我们的接口测试框架应该设法将接口的技术细节实现和业务参数进行拆分,并能自动处理与技术细节相关的部分,从而让业务测试人员只需要关注业务参数部分。

那要怎么实现呢?

在开始实现之前,我们不妨借鉴BDD(行为驱动开发)的思想,先想下如何编写接口测试用例的体验最友好,换句话说,就是让业务测试人员写用例写得最爽。

还是上面案例的接口测试用例,可以看出,最耗时的地方主要是计算签名校验值部分。按理说,签名校验算法我们是已知的,要是可以在测试用例中直接调用签名算法函数就好了。

事实上,这也是各种模板语言普遍采用的方式,例如Jinja2模板语言,可以在{% %}中执行函数语句,在{{ }}中可以调用变量参数。之前我在设计 [AppiumBooster][AppiumBooster] 时也采用了类似的思想,可以通过${config.TestEnvAccount.UserName}的方式在测试用例中引用预定义的全局变量。

基于该思路,假设我们已经实现了gen_random_string这样一个生成指定位数的随机字符串的函数,以及gen_md5这样一个计算签名校验值的函数,那么我们就可以尝试采用如下方式来描述我们的测试用例:

- test:
    name: create user which does not exist
    variable_binds:
        - TOKEN: debugtalk
        - random: ${gen_random_string(5)}
        - json: {"name": "user", "password": "123456"}
        - authorization: ${gen_md5($TOKEN, $json, $random)}
    request:
        url: http://127.0.0.1:5000/api/users/1000
        method: POST
        headers:
            Content-Type: application/json
            authorization: $authorization
            random: $random
        json: $json
    extract_binds:
        user_uuid: content.uuid
    validators:
        - {"check": "status_code", "comparator": "eq", "expected": 201}
        - {"check": "content.success", "comparator": "eq", "expected": true}

在如上用例中,用到了两种转义符:

  • $作为变量转义符,$var将不再代表的是普遍的字符串,而是var变量的值;
  • ${}作为函数的转义符,${}内可以直接填写函数名称及调用参数,甚至可以包含变量。

为什么会选择采用这种描述方式?(Why?

其实这也是我经过大量思考和实践之后,才最终确定的描述方式。如果真要讲述这个思路历程。。。还是不细说了,此处可省下一万字。(主要的思路无非就是要实现转义的效果,并且表达要简洁清晰,因此必然会用到特殊字符;而特殊字符在YAML中大多都已经有了特定的含义,排除掉不可用的之后,剩下的真没几个了,然后再借鉴其它框架常用的符号,所以说最终选择$${}也算是必然。)

可以确定的是,这种描述方式的好处非常明显,不仅可以实现复杂计算逻辑的函数调用,还可以实现变量的定义和引用。

除了转义符,由于接口测试中经常需要对结果中的特定字段进行提取,作为后续接口请求的参数,因此我们实现了extract_binds这样一个结果提取器,只要返回结果是 JSON 类型,就可以将其中的任意字段进行提取,并保存到一个变量中,方便后续接口请求进行引用。

另外,为了更好地实现对接口响应结果的校验,我们废弃了先前的方式,实现了独立的结果校验器validators。这是因为,很多时候在比较响应结果时,并不能简单地按照字段值是否相等来进行校验,除此之外,我们可能还需要检查某个字段的长度是否为指定位数,元素列表个数是否大于某个数值,甚至某个字符串是否满足正则匹配等等。

相信你们肯定会想,以上这些描述方式的确是很简洁,但更多地感觉是在臆想,就像开始说的gen_random_stringgen_md5函数,我们只是假设已经定义好了。就算描述得再优雅再完美,终究也还只是YAML/JSON文本格式而已,要怎样才能转换为执行的代码呢?

这就要解决How?的问题了。

嗯,这就是用例模板引擎的核心了,也算是ApiTestEngine最核心的功能特性。

更具体的,从技术实现角度,主要分为三大块:

  • 如何在用例描述(YAML/JSON)中实现函数的定义和调用
  • 如何在用例描述中实现参数的定义和引用,包括用例内部和用例集之间
  • 如何在用例描述中实现预期结果的描述和测试结果的校验

这三大块内容涉及到较多的技术实现细节,我们将在后续的文章中结合代码逐个深入进行讲解。

阅读更多

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 69 条回复 时间 点赞
Keith Mo 回复

方便留下 qq 吗?交流交流

jacexh 回复

那服务器用什么搭啊?

的确,关于参数签名,加密的确不方便,我现在是直接在代码里去处理,期望后续的更新

jacexh 回复

哈哈哈 谢谢 方便加 qq 吗?

楼主,拜读了你的源码,发现就是和 fitness 一样类似,建议你看看 fitness,在页面上写,和你的东西惊人的相似,而且也可以多线程,只需要 java 后台代码,原理就是 web 端通过关键字调用后台代码!

nicolas 回复

网上搜下就知道了,我们公司就用的这个,也可以和 jenkins 持续集成

尹全旺 回复

fitness 代码网址给个,我也去看看

尹全旺 回复

你说的是这个? http://fitnesse.org/
我大概看了下,差异还是挺大的。
后面你能写篇文章介绍下你们的实践方式么?期待

debugtalk 回复

和作者提供的其实是一样的,通过一系列关键字去定义接口脚本,也是定义数据,调用方法,具体用法看后台代码设计了,感觉你写的和我们公司用的太像了!不过用例最好打个标签吧!一个 suite 如果有些不想执行的话你这个就有限制了

Keith Mo 回复

我的经验是:压比较快的接口(<=10ms),locust 完全不可信

jacexh 回复

今天遇到了,看见 locust 里每个接口的平均响应时间都很整齐的 1 秒多,拿 nginx_status 页面试了一下,其他工具报告平均 4ms,locust 竟然报 300ms,结果完全没法信了

总之先提个 issue……,也给准备入坑的人看看这夸张的数字……
https://github.com/locustio/locust/issues/663


再来个真正的接口的例子,jmeter 200 线程 vs locust 200 协程

之前用 locust 做了两个项目都没有这么离谱,返回的数字都是比较合理的,今天不清楚什么情况……


jacexh 回复

大概知道上面是怎么回事了,似乎跟自己发请求自己监控有关,虽然电脑明明还有不少资源

用了 master/slave 就正常多了,对很快的请求这是不能接受的,10 倍时间……好在那种接口通常 ab 或 wrk 就够用

有空再翻翻源码看看怎么回事,另外那个 hatch rate 和 number of user 的设置跟实际的上限和增长率的关系也是个迷……

Keith Mo 回复

我觉得可能是 gevent 的锅,但我对这玩意不太感冒,没继续研究下去
在我们这边,locust 已经承担不了压测任务了,我们现在改用 golang 去做

jacexh 回复

你们是用现成的工具/框架还是自己造轮子?

换工具这种事超级烦,之前的脚本不通用: (
项目里已经用过 jmeter、gatling、locust,没一个满意,浪费不少时间,被老大骂很多次了……应付临时任务还是 jmeter 最适合,无论效率还是性能

有空准备看看 taurus,如果它的 yaml 用例格式比较合理就模仿一下,以后让新工具支持解析这种文件就不用那么烦了……

Keith Mo 回复

看到最后一句不禁泪流满面,我会努力的。
其实用 Locust 最爽的的确就是 Python + Requests 了,实现各种业务逻辑都很方便,但是当脚本多了以后,会发现也存在挺多重复的,所以我在 ApiTestEngine 中采用 YAML 来描述用例,尽量消除了不必要的重复,同时也复用了 Requests 的强大功能,在跑的时候采用 locusts 命令,直接就能跑压测了,感觉应该是挺不错的特性的😅

请教一个问题:

variables:
      - params:
          action: createxxxxxxx
          stationName: 测试
          shortName: 测试

类似于上述的 variables,可以使用 $params.stationName 来引用参数吗
我尝试了一下是报错的,不知道是不支持还是引用方式不对呢?

riku 回复

不支持

请问,是否支持接口的请求参数是文件的情况,目前不知道要怎么进行上传。直接录制然后 har2case 的数据使用 httprunner 运行也会报错。 @debugtalk

yjq 回复

找到方法了😃

simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 12月13日 20:49
simple [精彩盘点] TesterHome 社区 2018 年 度精华帖 中提及了此贴 01月07日 12:08
思寒_seveniruby 将本帖设为了精华贴 07月08日 07:47

缩减版 robotframework

已经写得差不多了,只是还要疏通下逻辑

ting 回复

roboframework 能实现压测么?先剧透下,APITestEngine 可以很好的实现哦,而且是直接基于同一份用例。
例如:

#coding: utf-8
import zmq
import os
from locust import HttpLocust, TaskSet, task
from ate import utils, runner

class WebPageTasks(TaskSet):
    def on_start(self):
        self.test_runner = runner.Runner(self.client)
        self.testset = self.locust.testset

    @task(weight=10)
    def test_login(self):
        results = self.test_runner.run_testset(self.testset)
        assert results[0] == (True, [])

class WebPageUser(HttpLocust):
    host = 'http://www.skypixel.com'
    task_set = WebPageTasks
    min_wait = 1000
    max_wait = 5000

    testcase_file_path = os.path.join(
        os.getcwd(), 'testcases/skypixel/open_app.yml')
    testsets = utils.load_testcases_by_path(testcase_file_path)
    testset = testsets[0]
debugtalk 回复

而且再仔细看这个压测脚本实现方式,会发现模板性很强,所以这完全可以自动生成。也就是说,只需要维护一套YAML/JSON格式的用例,就可以同时实现接口自动化和性能压测了。

压测这里打算集成 Locus?

是的,直接复用的,现在最新的代码已经有了这个特性了,只是还没写文档

你好 我在看你共享的源码 有个问题 我没看明白 下面几个文件有啥区别

demo_import_functions.yml demo_template_separate.yml demo_template_sets.yml
设计初衷是什么 多谢

板凳搬好,等更新

Sunny 回复

这些只是用来测试框架的数据而已。分别对应着不同的特性,例如单个测试用例,测试用例集。另外,由于代码比文档超前很多,很多特性还没来得及介绍。

如果是基于依赖的接口应该怎么弄呢?

Karaser 回复

数据提取方式这部分,我借鉴的是 pyresttest 框架的方式,你可以看下代码的单元测试中用到的例子。

def test_extract_response_json(self):
    resp = requests.post(
        url="http://127.0.0.1:5000/customize-response",
        json={
            'headers': {
                'Content-Type': "application/json"
            },
            'body': {
                'success': False,
                "person": {
                    "name": {
                        "first_name": "Leo",
                        "last_name": "Lee",
                    },
                    "age": 29,
                    "cities": ["Guangzhou", "Shenzhen"]
                }
            }
        }
    )

    extract_binds = {
        "resp_status_code": "status_code",
        "resp_headers_content_type": "headers.content-type",
        "resp_content_body_success": "body.success",
        "resp_content_content_success": "content.success",
        "resp_content_text_success": "text.success",
        "resp_content_person_first_name": "content.person.name.first_name",
        "resp_content_cities_1": "content.person.cities.1"
    }
    resp_obj = response.ResponseObject(resp)
    extract_binds_dict = resp_obj.extract_response(extract_binds)

    self.assertEqual(
        extract_binds_dict["resp_status_code"],
        200
    )
    self.assertEqual(
        extract_binds_dict["resp_headers_content_type"],
        "application/json"
    )
    self.assertEqual(
        extract_binds_dict["resp_content_content_success"],
        False
    )
    self.assertEqual(
        extract_binds_dict["resp_content_text_success"],
        False
    )
    self.assertEqual(
        extract_binds_dict["resp_content_person_first_name"],
        "Leo"
    )
    self.assertEqual(
        extract_binds_dict["resp_content_cities_1"],
        "Shenzhen"
    )
Sunny 回复

当前是这样的,测试数据与测试用例是一起的

卡斯 回复

卡斯大叔起得好早啊,膜拜

debugtalk 回复

那就类似例中填写 A 接口的左右边界值,后台提取后用在 B 接口中是吧,那放在 B 接口的哪个位置呢?这个是不是还是用左右边界值来确定呢?另外还想问,以数据驱动的话,用例应该写在哪里呢?Excel?还是 Cucumber 框架,亦或是有什么其他办法?

Karaser 回复

用例编写方式,推荐的是YAML/JSON的格式。写好后放置到任意文件夹中,然后通过如下方式执行即可。

$ python main.py --testcase-path TESTCASE_FOLDER

具体的使用方法部分我还在写。

测试数据是如何存放的,与编写的用例一起存放于同一份 yaml 文件中吗

Karaser 回复

你说的依赖接口指的是这种情况么?B 接口的参数需要 A 接口返回的值。
针对这种情况,我的实现方式是,在用例配置的时候可以选择将接口返回结果的特性字段提取出来,绑定到一个变量中,然后后续接口请求就可以直接引用了。

debugtalk 回复

了解,谢谢~!

国文 回复

好了,已经添加了

debugtalk 回复

OK,了解了,还有个问题:
假设要遍历测试某个参数不同值(如 password),你这边是如何实现的?

Sunny 回复

我明白你的意思,是要同一个测试用例,对应多个不同的测试数据对吧?
这块儿暂时还没有特殊处理,暂时还是以一个测试用例对应一种测试数据组合的形式。
后面的想法有考虑通过函数调用来获取测试数据,例如在特定范围内获取随机值,或者从数据库表中获取特定字段。

第一个问题,参考这个 issue
第二个问题,可以将数据库查询封装到函数调用里面,然后参考《ApiTestEngine (3)》

Sunny 回复

是采用这种方式运行的么?
python -m unittest test.test_apiserver_v2.py

执行文件 test_apiserver_v2.py,运行至启动 flask mock server 时,报如下错误,能帮忙看下是哪里的问题吗?

Traceback (most recent call last):
File "", line 1, in
File "E:\Program Files\python3\lib\multiprocessing\spawn.py", line 105, in spawn_main
exitcode = main(fd)
File "E:\Program Files\python3\lib\multiprocessing\spawn.py", line 115, in _main
self = reduction.pickle.load(from_parent)
EOFError: Ran out of input
Failure
Traceback (most recent call last):
File "E:\Program Files\python3\lib\unittest\suite.py", line 163, in _handleClassSetUp
setUpClass()
File "E:\Program Files\JetBrains\PyCharm Community Edition 2017.1.1\project\ApiTestEngine-master\test\base.py", line 21, in setUpClass
cls.api_server_process.start()
File "E:\Program Files\python3\lib\multiprocessing\process.py", line 105, in start
self._popen = self._Popen(self)
File "E:\Program Files\python3\lib\multiprocessing\context.py", line 223, in _Popen
return _default_context.get_context().Process._Popen(process_obj)
File "E:\Program Files\python3\lib\multiprocessing\context.py", line 322, in _Popen
return Popen(process_obj)
File "E:\Program Files\python3\lib\multiprocessing\popen_spawn_win32.py", line 65, in __init
_
reduction.dump(process_obj, to_child)
File "E:\Program Files\python3\lib\multiprocessing\reduction.py", line 60, in dump
ForkingPickler(file, protocol).dump(obj)
TypeError: can't pickle _thread.lock objects

debugtalk 回复

对的

Sunny 回复

看你贴的堆栈日志,报的错都不是框架部分的,不确定你那边的环境是否正常。
不过我也在 Mac 和 Linux 系统上测试过,后面也找台 Windows 试下是不是有兼容性问题。

后面还是要先写使用说明文档了,实现细节关注的人好少

请问下,为啥不把 token 等加密信息封装在代码里面,而是放在了测试用例上呢?这样做会不会导致加密信息容易泄露?封装在代码上的话,判断需要带上签名的接口再调用这个加密算法即可,不知道这个方法对不对,还请指教下~🙋 🙋

Ningxw 回复

这里的 token 表述的确不准确;我已经修改了案例,你可以直接看最新的代码,在 README 中的案例:https://github.com/debugtalk/ApiTestEngine

debugtalk 回复

好的,我去下载看看~期待文章的下一个系列~

1楼 已删除
14楼 已删除

到 wiki 建个合集吧😀

国文 回复

我看了下,貌似没有权限

debugtalk 回复

好了,可以试试

匿名 #47 · 2017年07月14日

怎么获取上一个用例里生成的值用在下一个用例里?怎么从数据库查询值用在用例里?
感觉没有这两样简直做不下去,n 多接口有数据依赖

挺好的,期待完结

皇甫春峰 回复

nginx 即可

location /benchmark {
    return 200 'pong!'
}

准备仿造您的文章,自己再造一遍轮子。
不过我只会 java,看不太懂 python 代码。

现在暂时还卡在这篇文章这里的,最近每天晚上都会先打开这篇文章看看。
现在用例基本是按照你的这个模式来的,不过是写的 json。

现在我们接口的自动化已经做了一部分了,TestNG+reportNG+jenkins+testlink+restassured,不过是那种全靠写代码的。需要一个新的用例,就自己去加一个@Test

现在想造一遍别人可能做过很多次的轮子。
加上测试用例的管理,然后加一个 web 来作为测试用例新增、修改、执行的后台。
太多纠结的地方了,太多需要权衡的地方了。稍稍考虑下灵活性,就会复杂很多倍。

白虹李李 回复

不用这么跟自己过不去啊,Java 和 Python 的语言特性差异本来就比较大,如果照搬 Python 实现的特性再采用 Java 来实现,可能会非常费事。个人建议,如果用 Java,那么就尽量发挥 Java 的特长,采用 Java 的方式去做就好啦。

白虹李李 回复

好的,多多交流,你也可以加我微信 leolee023

debugtalk 回复

并不是说要把 python 改写为 java。
只是我也刚学这个,这是我现在唯一看到,讲得清楚,又有源代码的项目。
所以有疑惑的时候就过来研究,看看你是怎么处理的:)

如果有 java 的开源代码可以学习当然更好了,目前还没找到。

debugtalk 回复

多谢。
我现在写了一部分解析测试用例的代码了。

我的 sign 比较复杂,需要用到实时产生的 timestamp、传入的 appkey、消息体 body 本身(或者 query string 排序)。
timestamp 可以作为 variable,但是 body(或者 query string 排序)就不好办了。

总感觉需要多次扫描测试用例,或者按照特定的顺序来读取测试用例。 —— 我是计算了函数后将结果替换回去,再来扫描的

反正就是特别不爽,感觉这个 sign 要特殊处理。

白虹李李 回复

昨天晚上想了又想,发现是我太纠结要通用了。想着换一套接口也能用。

本身能力有限,却想着做出来要尽量通用,或者看到别人是怎么怎么样的,难免患得患失。
其实我现在应该尽量少的扩展,将已有的底层功能一一进行封装,然后再根据情况考虑别的事情。
准备回头去修改代码去了:)

locust 能复用是因为你们接口框架用的是 requests 吧,这个方案我们两年多前就用了。但 locust 性能很差,真心不推荐

jacexh 回复

性能再差也比LR/Jmeter强,一般的业务系统也上不了多大的并发,再不济还可以分布式,所以在压测工具的性能和易用性方面,我优先考虑后者。

jacexh 回复

在你给的 issue 链接里面,原作者也回答了,and the optimization is unnecessary in most cases.。就大多数系统的并发性能之低,还用不着压测工具拼性能。

debugtalk 回复

我不知道你觉得 locust 性能强于 jmeter 这种结论是哪来的,你可以去搜下 locust issue,质疑其性能的有很多,如:
https://github.com/locustio/locust/issues/586

根据我很早之前的测试,locust 单节点 QPS 上限约在 600 QPS 左右,而且 response time 明显异常
也就是说在一台 4 核的 PC 机上,跑 4 个节点的测试,至多产生 2400 QPS
而 jmeter 却可以产生 22K QPS

当然 jmeter 基于 java 线程的并发也不算优秀,ab/wrk/golang 能达到 50K+QPS

这边实测的结果也是现在被人看好的 gatling 和 locust 真的没法跟被鄙视的 jmeter 比极限性能

jmeter 从 2.x 开始每个小版本都在优化性能和采用更合理的默认配置,现在 3.2 版简单调下 JVM 参数,测部署在配置一般的服务器上的 hello world 或 nginx_stub,不比 ab/wrk 差多少

我测的结果是 ab/wrk 34k 左右,jmeter 26k-29k,号称高性能的 gatling 14-22k,locust + zeromq + 8 个 slave 才 2-3k……

但!是!jmeter 太旧了,诞生的时候 NIO 还没出来,收发请求是同步的,被测程序响应慢就会导致一堆线程堵塞,没有足够的压力到服务器,工具本身再快也没用。我们很少会去测静态页或从 redis 读数据直接返回的接口,多数时候它的表现都不能让人满意


gatling 最近在用,本来想全面转向它的,越用越不对劲,before()after()里不支持 gatling 的 DSL,要用原生 scala,这会导致同一个接口在准备数据和跑测试时要以不同的方式写 2 次,复用起来也很麻烦

被 requests 惯坏了的人看到 scala 里的各种 http 库的 api 就倒胃口,碰都不想碰,估计只有 java 程序员忍得了(逃

它的分布式也相当简单粗暴:在不同机器开几个,跑完把 log 文件拼起来

另外今天才发现它没法做到真的并行测多个接口,比如已知 4 个 RPS 差不多,互相无依赖的接口放进去,结果 A >> B >> C >> D,后面每个都只有前一个的一半左右,issue 在这:https://github.com/gatling/gatling/issues/2336

最后就是我也不懂 scala,暂时弃坑╮(╯▽╰)╭


locust,很恶心也很舒服……官方文档没多详细,例子极少,必须要看源码和踩过坑的人写的教程才能入门……

它只有一个骨架,几乎什么都不提供要自己实现,好听点自由,不好听寒碜……

但是 python + requests 顺手啊!╮(╯▽╰)╭

开发效率实在高太多了,尤其像我这样主要测单个接口、单个模块/微服务而不是一长串乱七八糟的业务的,可以一堆不相关接口放一块运行,RPS 跟 gatling 测单个接口没什么区别,节省大量时间

谁叫绝大多数程序的效率比测试工具低得多呢╮(╯▽╰)╭

对于目标是在开发过程中尽早暴露问题而不是验收的人来说,locust 的缺点可以接受,之后基本就用它了

期待有一天 ApiTestEngine 的热度超过 locust 本身 XD

mark 先。辛苦了。

Keith Mo 回复

我觉得最大的问题不是 locust 产生的压力小,而是其 response time 明显偏高,特殊情况偏高数十倍
结果很难信任

jacexh 回复

后面我也做了 benchmark 对比看下。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册