前言
groovy脚本
ngrinder 的 groovy 脚本是顺序结构的,用户可通过编写脚本执行过程中被预置的函数进行用户操作,完成各种复杂的测试工作。
ngrinder 的进程与线程
ngrinder 使用进程和线程来模拟多个用户。例如,如果您设置了如下的测试。只有一个代理将被激活,1个进程将被调用,然后这个进程将包括2个运行线程。每个线程的行为就像1个用户。因此,2个虚拟用户正在运行。如果将代理计数增加到2,则总共有4个虚拟用户(Vusers)。
并发量=代理数x进程数x线程数
如果在Vuser per agent 中输入总的虚拟用户数时,nGrinder根据内部算法,会进行适当的计算,如输入100,当agent数为1时, 会变成99,该算法可以通过 process_and_thread_policy.js 这个文件来修改。
若果agent 的内存4G以下的话,建议进程不要超过10个,线程数不要超过200.
- 官方最新测试:4G内存的agent 最多可以模拟4000 虚拟用户。
预置函数
依据上面对agent、进程与线程的解释,就比较好理解ngrinder groovy 脚本的
结构了。
控制器将脚本分发给agent,每个agent按照算法启动对应数量的进程,每个进程里在启动对应数量的线程,执行测试任务。
注解 |
描述 |
执行次数 |
用例 |
@BeforeProcess |
在进程被调用之前执行的函数 |
每进程一次 |
加载被线程共享的资源文件,定义 公共变量等 |
@AfterProcess |
在进程被终止之前执行该函数 |
每进程一次 |
关闭资源文件 |
@BeforeThread |
在每个线程被调用之前执行的函数 |
每线程一次 |
登录目标系统,建立线程内的一些值,例如,Cookie 处理 |
@AfterThread |
在每个线程被终止之前执行的函数 |
每线程一次 |
退出系统 |
@Before |
每个被 @Test 注解的方法被执行前应执行的函数 |
同虚拟用户数 |
每个被 @Test 注解的方法的共享逻辑、变量设置 |
@After |
每个被 @Test 注解的方法被执行后应执行的函数 |
同虚拟用户数 |
很少使用 |
@Test |
主测试行为,将被被执行多次 |
同虚拟用户数 |
测试体 |
groovy 脚本实例
压测实例
/*
这个脚本是对需要验签接口的压测
*/
import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
import net.grinder.plugin.http.HTTPRequest
import net.grinder.plugin.http.HTTPPluginControl
import net.grinder.script.GTest
import net.grinder.script.Grinder
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
// import static net.grinder.util.GrinderUtils.* // You can use this if you're using nGrinder after 3.2.3
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import java.util.Date
import java.util.List
import java.util.ArrayList
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import HTTPClient.Cookie
import HTTPClient.CookieModule
import HTTPClient.HTTPResponse
import HTTPClient.NVPair
import java.text.SimpleDateFormat;
import org.apache.commons.codec.binary.Base64;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Arrays;
import org.apache.commons.lang.StringUtils;
import java.lang.StringBuilder
import java.io.UnsupportedEncodingException;
/**
* A simple example using the HTTP plugin that shows the retrieval of a
* single page via HTTP.
*
* This script is automatically generated by ngrinder.
*
* @author admin
*/
@RunWith(GrinderRunner)
class TestRunner {
public static GTest test
public static HTTPRequest request
// 定义全局变量
public static NVPair[] params = []
public static Cookie[] cookies = []
public static String private_key
public static String[] contents = []
@BeforeProcess
public static void beforeProcess() {
HTTPPluginControl.getConnectionDefaults().timeout = 6000
test = new GTest(1, "Test1")
request = new HTTPRequest()
// 获取加密私钥内容
contents = new File("./resources/rsa_private_key_pkcs8.pem") as String[]
StringBuilder private_str = new StringBuilder();
for(int i=0;i<contents.length;i++){
if (contents[i].charAt(0) != '-' && contents[i] != null) {
private_str.append(contents[i]).append("\n");
}
}
private_key = private_str.toString()
//调试输出
grinder.logger.info("before process.");
}
@BeforeThread
public void beforeThread() {
test.record(this, "test")
grinder.statistics.delayReports=true;
grinder.logger.info("before thread.");
}
//自定义函数,修改http头数据
private NVPair[] headers(post) {
if( post != null ){
post="&"+post
}else{
post=""
}
def appid = "1111111111111111111111"
def appcode = "11111"
long currentTime = System.currentTimeMillis()
def json = "appId="+appid+"&appCode="+appcode+"&"+""+"timestamp="+currentTime+post
def sign = getsign(json)
//grinder.logger.info(currentTime.toString())
return [
new NVPair("Content-type", "application/json;charset=UTF-8"),
new NVPair("appId", "1013f9d4e97026cb07e3fdea1b560f2f"),
new NVPair("sign", sign),
new NVPair("timestamp", currentTime.toString()),
new NVPair("appCode", "11111")
];
}
//自定义函数,生成YYYY-MM-dd HH:mm:ss 格式的当天日期串
def today()
{
String str = "";
SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
Calendar lastDate = Calendar.getInstance();
str = sdf.format(lastDate.getTime());
return str;
}
// 自定义函数加签
private getsign(post){
//加签算法 略
return sign
}
@Before
public void before() {
request.setHeaders(headers())
cookies.each { CookieModule.addCookie(it, HTTPPluginControl.getThreadHTTPClientContext()) }
grinder.logger.info("before thread. init headers and cookies");
}
@Test
public void test(){
// 参数初始化
def domain = "api.xxxx.com"
def timestr=today()
def json='{"eventCode":"1111111", "eventTime":"'+timestr+'", "channelCode":"1111111" , eventTime":"'+timestr+'"}}';
//获取签名后的头信息
def head = headers("bizContent="+json)
//grinder.logger.info(head.toString())
HTTPResponse result = request.POST('http://'+domain+'/channel/v1/receive',json.getBytes(), head)
//HTTPResponse result = request.GET("http://10.14.8.111/phpapi.php", params)
if(result.text.equals(new String(result.text.getBytes("iso8859-1"), "iso8859-1")))
{
result.text=new String(result.text.getBytes("iso8859-1"),"utf-8");
}
//调试输出头信息
grinder.logger.info("result="+request.getHeaders())
//调试输出结果数据
grinder.logger.info("result="+result.text)
if (result.statusCode == 301 || result.statusCode == 302) {
grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", result.statusCode);
} else {
assertThat(result.statusCode, is(200));
}
}
}