性能测试工具 nGrinder - Groovy 脚本指南

Keith Mo · 2018年03月05日 · 最后由 会飞的猪 回复于 2019年01月03日 · 4695 次阅读

我的博客搬运

劝退帖 :P


nGrinder 是对 Grinder 的封装,本来 Grinder 只支持 Jython,nGrinder 3.2.1+ 加入了对 Groovy 的支持,用的是改造过的 JUnit 框架,比原版脚本强大得多。

nGrinder 架构简介, 2017-03

nGrinder 的 Groovy 脚本使用指南(Groovy 脚本结构), 2017-03

nGrinder 的 Groovy 脚本使用指南(Groovy maven 结构), 2017-03

https://github.com/naver/ngrinder/wiki/Groovy-Script-Structure

更多高级用法:

https://github.com/naver/ngrinder/wiki/Scripting-Guide

https://github.com/naver/ngrinder/wiki/Groovy-Script-Snippet

这篇文章的作用基本上是标出雷区劝人绕道——对不是专职做性能测试的人来说,要写/改代码的场景都太麻烦,难以发挥 nGrinder 开箱即用的优势。


脚本结构

@RunWith(GrinderRunner)  // 每个测试类加这注解
class TestRunner {
    @BeforeProcess  // 在每个进程启动前执行
    public static void beforeProcess() {
        // 加载资源文件、初始化 GTest 等
    }

    @BeforeThread  // 在每个线程执行前执行
    public void beforeThread() {
        // 登录、设置 cookie 之类
    }

    @Before  // 在每个 @Test 注解的方法执行前执行
    public void before() {
        // 设置变量、多个 @Test 方法共用的逻辑等
    }

    @Test  // 在测试结束前不断运行。各个 @Test 注解的方法异步执行。
    public void foo() {
        // ...
    }

    @Test
    public void bar() {
        // ...
    }

    @After  // 在每个 @Test 注解的方法执行后执行
    public void after() {
        // 很少用到
    }

    @AfterThread
    public void afterThread() {
        // 登出之类
    }

    @AfterProcess  // 在每个进程结束后执行
    public static void afterProcess() {
        // 关闭资源
    }

执行顺序

(图片来源:nGrinder 官方 wiki)


  • 方法 1:通过 UI 设置:脚本 -> 新建脚本 -> 显示高级配置。
    • User-Agent 头比较方便,输入框里双击就出来默认值。
  • 方法 2:改脚本:

2-A. 在 @BeforeProcess 注解的方法里添加:(相当于方法 1)

List<NVPair> headerList = new ArrayList<NVPair>()
headerList.add(new NVPair("Content-Type", "application/json"))
// ...
headers = headerList.toArray()

2-B. 修改开头的成员变量:

public static NVPair[] headers = [
    new NVPair("Content-Type", "application/json")
]

2-C. 在 @Test 注解的方法的请求参数里添加:

def result = request1.POST("http://example.com",
    params,
    [new NVPair("Content-Type", "application/json")] as NVPair[])

gzip

// nGrinder 不会自动解压,通常在 @beforeProcess 注解的方法里加上:
HTTPPluginControl.getConnectionDefaults().setUseContentEncoding(true)

// 然后在设 header 的地方加上:
headerList.add(new NVPair("Accept-Encoding", "gzip"))

请求参数

form

  • 方法 1:通过 UI 设置:脚本 -> 新建脚本 -> 显示高级配置
  • 方法 2:改脚本:

2-A. 在 @BeforeProcess 注解的方法里添加:(相当于方法 1)

List<NVPair> paramList = new ArrayList<NVPair>()
paramList.add(new NVPair("accessToken", accessToken))
// ...
params = paramList.toArray()

2-B. 修改开头的成员变量:

public static NVPair[] params = [
    new NVPair("accessToken", "9de6d5f2b62d4bb686de8033fd00bc65")
]

2-C. 在 @Test 注解的方法的请求参数里添加:

def result = request1.POST("http://example.com",
    [new NVPair("accessToken", accessToken)] as NVPair[],
    headers)

JSON

def json = '{"userId": ["100180"]}';
request.POST("http://you_api_url", json.getBytes(), [
    new NVPair("Content-Type", "application/json"
] as NVPair[])

http://grinder.sourceforge.net/g3/script-javadoc/net/grinder/plugin/http/HTTPRequest.html

通过 UI 传參

限制很多,只能传 1 个参数(保存到名为 param 的属性),值只能由 1-50 个数字字母、_,.| 组成,不能有空格。

// 获取 UI 传入的字符串
def foo = System.getProperty("param")

// 带默认值
def foo = System.getProperty("param", "XXX")

// 3.2.3+ 可以放开自动生成的脚本里 import static net.grinder.util.GrinderUtils.* 的注释
/* GrinderUtils 类有以下静态方法:
getParam()
getParam(String)
getParamInt()
getParamLong()
getParamFloat()
getParamDouble()
getParamBoolean()
*/

https://github.com/naver/ngrinder/wiki/How-to-pass-a-parameter-to-the-script


返回内容

断言

使用 JUnit 的断言:

def result = request.GET("http://www.google.com")

// HTTP 状态码
assertThat(result.statusCode, is(200))
// 响应体
assertThat(result.text, containsString("google"))

解析 JSON

假设服务器返回以下内容,成功标志是 "resultCode": 1

{
    "data": {
        "token": "9de6d5f2b62d4bb686de8033fd00bc65"
    },
    "resultCode": 1
}
import groovy.json.JsonSlurper

// ...

def jsonData = new JsonSlurper().parseText(result.text)
assertThat(jsonData.resultCode, is(1))

https://github.com/naver/ngrinder/wiki/How-to-parse-JSON

提取参数

GrinderRunner 每个线程只创建 1 个测试用例对象,所以 @Test 注解的方法可以共享成员变量:

private String token

@Test
public void test1() {
    // ...
    if (jsonData.resultCode == 1) {
        token = jsonData.data.token
        test2()
    }
}

// 希望顺序执行的场合,不加 @Test,让别的方法调
public void test2() {
    // 打 log
    grinder.logger.info(this.token)

    def result = request.POST("http://192.168.3.231/auth2/v2/user/getSimpleUser", 
        '{"userId": ["100180"]}'.getBytes(),
        [
            new NVPair("Content-Type", "application/json",
            new NVPair("access-token", this.token)
        ] as NVPair[]
    )
    // ...
}

【注意】目前这块的支持很差,不推荐这样用:

  • 如果 1 个脚本里调了多个接口,详细报告里看不出这些接口分别是什么、响应时间和 TPS 分别多少。
  • 如果某个方法断言成功,即使其他方法抛异常也不会反映在 UI 的错误数里,看 log 才能发现问题。

定义多个事务

【注意】目前 UI 对这块的支持很差,详细报告里只有总体情况,想看各个事务的数据只能下载 CSV 自己写程序解析。

public static GTest test1
public static GTest test2

// ...

@BeforeProcess
public static void beforeProcess() {
    test1 = new GTest(1, "foo_stats")  // 参数为 ID、显示名
    test2 = new GTest(2, "bar_stats")
    // ...
}

@BeforeThread
public void beforeThread() {
    test1.record(this, "foo")  // 参数为 this、@Test 注解的方法名
    test2.record(this, "bar")
    // ...
}

@Test
public void foo() {
    // ...
}

@Test
public void bar() {
    // ...
}

不同权重

@RunRate(50)  // 数字代表百分比
@Test
public void test1() {
    // ...
}

@RunRate(20)
@Test
public void test2() {
    // ...
}

如果权重加起来不到 100,剩下的时间什么也不干。


修改默认脚本模板

nGrinder 没有放出模板,只能改源码:

修改 ngrinder-controller/src/main/resources/script_template/basic_template_groovy.ftl 内容,重新打包。


资源文件

在脚本文件同级目录下建 resources 目录,上传资源文件。

【缺点】多个脚本使用同样的资源文件时不方便,只能把这些脚本放在同一个目录下,除非建 Groovy Maven 工程。

文本:

static String text
@BeforeProcess
public static void beforeProcess() {
    // ...
    text = new File("./resources/foo.txt").text
}

https://github.com/naver/ngrinder/wiki/How-to-use-resources

CSV:

非常麻烦,建议换个工具:

nGrinder 参数使用, 2017-08


外部库

在脚本文件同级目录下建 lib 目录,jar 包放进去就能在脚本里引用。

【缺点】多个脚本使用同样的库时不方便,只能把这些脚本放在同一个目录下,除非建 Groovy Maven 工程。

https://github.com/naver/ngrinder/wiki/How-to-use-library


Groovy Maven Structure

https://github.com/naver/ngrinder/wiki/Groovy-Maven-Structure

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

选型参考:

优点:

  • 无需安装、跨平台:总共就 controller、agent、monitor 3 个 jar 包。
  • 开箱即用,web UI 比较友好,测最简单的 GET 请求上手门槛低。
  • 有简单的可视化报告。
  • 有简单的资源监控。
  • 有简单的脚本管理功能,整合了 SVN。
  • 容易扩容:只需要把 agent 放到压测机器,monitor 放到被测服务器。

缺点:

  • 发送请求
    • 首页只支持 GET,POST 需要新建脚本然后创建测试。
  • 脚本管理
    • 脚本文件、目录没法重命名,没法移动,只能删掉重新建一份,或通过 SVN 操作。
    • 如果多个脚本使用同样的库和资源文件,UI 里不好搞(除非全塞同一个目录下),需要建完整的 Maven 工程。
  • 报告
    • 1 个测试脚本里调多个接口时,报告里看不出这些接口分别是什么、响应时间和 TPS 分别多少。
    • 1 个测试脚本里调多个接口时,只要某个测试方法成功,即使其他方法抛异常也不会反映在报告的错误数里,要看 log 才能发现。
    • 脚本里可以定义多个事务,但详细报告里只有总体情况,想看各个事务的数据只能下载 CSV 自己写程序解析。
    • 响应时间没有按百分位数的统计,需要二次开发。
    • 服务器资源监控收集的数据较少,需要二次开发。
    • 想改默认脚本模板很麻烦,需要改源码然后重新打包。

总结:

如果非专职做性能测试的人,如开发/运维/业务测试希望有一个简单的工具来定位问题,而且项目用 HTTP 协议、没有很麻烦的鉴权、签名、加密等限制、GET 请求又足够多,nGrinder 可以用。易用性至少跟 PTS、WeTest 一个水平,nGrinder 要写代码的场景这 2 个平台或者其他工具多半同样要写。

专职的话它肯定不是首选,开发起来不够快,定位问题也不够方便,有其他更合适的选择。


我不懂 Groovy,以上全是我编的,我实在编不下去了……😆

大神 groovy 多接口混合压测,怎么设置的。

xinxi nGrinder 入门 中提及了此贴 01月31日 15:03
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册