书接上文分布式性能测试框架用例方案设想(一)​,方案二进行更加复杂的测试用例,以​​jar包类方法​​形式的用例进行测试,下期会基于​​docker​​进行技术验证。

Part1基于jar包类方法的压测场景

性能测试脚本基于​​FunTester​​性能测试框架,今天分享提前将用例写好,暴露部分参数的用例。这类性能测试用例的测试场景已经提前设定,参数化目前都只是在测试线程数,软启动时间,终止条件等做了区分等等。

这类用例的修改增添都需要重新部署服务,所以比较适合稳定用例,比如基准性能测试用例这些,可用于性能巡检、定期性能回归等等测试场景。这种用例形式不同于用例方案设想(一)中的单请求和多请求模式,脚本的话已经可以支持更多的用例场景,包括单链路多链路全链路测试。对于每次请求都需要签名的接口也是非常不错的选择。

PS:彻底放弃了本地执行​​shell​​命令的方式,太复杂,不可控的东西比较多。

Part2实现Demo

这里我自己写了一个测试类,实现了无参方法基础类型参数方法String对象类型参数String数组类型参数四种方法的反射执行的​​Demo​​,功能基于​​com.funtester.frame.execute.ExecuteSource​​类,这个主要功能就是执行​​jar​​包内的方法,这个类的代码我会放在最后,大家也可以点击阅读原文查看仓库中的最新代码。

下面是测试类的代码:


public class Test3 extends SourceCode {

private static final Logger logger = LogManager.getLogger(Test3.class);

public static void main(String[] args) {
ExecuteSource.executeMethod("com.funtest.javatest.Test3.funtest");
ExecuteSource.executeMethod("com.funtest.javatest.Test3.funtest", "java.lang.Integer", "1");
ExecuteSource.executeMethod("com.funtest.javatest.Test3.funtester", "java.lang.Integer", "1");
ExecuteSource.executeMethod("com.funtest.javatest.Test3.funtester", "java.lang.String", "33,3234,43,24,23,4,22");

}


public static void funtest() {
output(Time.getDate());
}


public static void funtest(Integer i) {
output(i + TAB + Time.getDate());
}

public static void funtester(int i) {
output(i * 10 + TAB + Time.getDate());
}

public static void funtester(String[] i) {
output(i.length * 10 + TAB + Time.getDate());
}

public static void funtester(String i) {
funtester(i.split(COMMA));
}


}

控制台输出:

INFO-> 当前用户:oker,工作目录:/Users/oker/IdeaProjects/funtester/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
INFO-> 2021-05-13 18:24:05
INFO-> 1 2021-05-13 18:24:05
WARN-> 方法属性处理错误!
INFO-> 10 2021-05-13 18:24:05
INFO-> 70 2021-05-13 18:24:05

Process finished with exit code 0

其中这个报错,在之前的文章中提到过有兴趣大家可以参考:反射执行Groovy类方法NoSuchMethodException解答,当然直接忽略也是可以的。

这里有个坑,​​Java​​​反射调用某个方法时,对于引入了基础类型(如​​int​​​)参数的重载方法识别不准,有时候能找到,有时候找不到,我都怀疑是随机的。但是改成​​java.lang.Integer​​​参数类型就完全没问题,这一点以后写用例的时候需要多多注意。这里还有个坑,​​Java​​​反射执行方法参数是​​String[]​​​的时候,始终无法传参成功,我试了好几种方式,现在果断放弃了。因为我的用例都是写在​​Groovy​​​类中的​​main​​​方法中。所以将来需要对​​jar​​包内的所有用例添加一个方法:

  /**
* @param args
*/
public static void main(String args) {
main(args.split(COMMA));
}

Part3用例创建

1单接口

对于简单的用例的话,标准的用例编写格式如下:

public static void main(String[] args) {
ClientManage.init(60, 60, 0, "", 0);
ArgsUtil util = new ArgsUtil(args);
int thread = util.getIntOrdefault(0, 2);
int times = util.getIntOrdefault(1, 10);
Constant.RUNUP_TIME = util.getIntOrdefault(2, 10);
MySqlTest.LOG_KEY = false;
String url = "http://localhost:12345/test/qps";
JSONObject params = new JSONObject();
params.put("name", "FunTester");
params.put("password", "123456798");
Sign.sign(params);
HttpGet httpGet = getHttpGet(url,params);
HttpGet httpGet = getHttpGet(url);
RequestThreadTimes requestThreadTimes = new RequestThreadTimes(httpGet, times, null);
Concurrent funTester = new Concurrent(requestThreadTimes, thread, "FunTester测试反射执行");
funTester.start();
}

依然采用​​com.funtester.frame.thread.RequestThreadTimes​​多线程任务类对象作为演示,而且是​​Java​​的,不过这个类已经被我标记过期了,我重新写了一个多线程对象,更加适合现在公司的项目,并且兼容了​​com.funtester.frame.thread.RequestThreadTimes​​和​​com.funtester.base.constaint.ThreadLimitTimeCount​​类的功能。

2多接口和链路测试

这两个区分度不大,需要额外单独实现被测方法,大家可以看看单链路性能测试实践文章中的链路设计和实现思路。这里只分享一下脚本内容,如下:

import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONObject
import com.funtester.base.bean.AbstractBean
import com.funtester.base.constaint.ThreadLimitTimesCount
import com.funtester.frame.execute.Concurrent
import com.funtester.httpclient.ClientManage
import com.funtester.utils.ArgsUtil
import com.okayqa.composer.base.OkayBase
import com.okayqa.composer.function.Mirro
import com.okayqa.composer.function.OKClass

class Login_collect_uncollect extends OkayBase {

public static void main(String[] args) {
ClientManage.init(10, 5, 0, "", 0)
def util = new ArgsUtil(args)
def thread = util.getIntOrdefault(0, 30)
def times = util.getIntOrdefault(1, 40)

def tasks = []

thread.times {
tasks << new FunTester(it, times)
}

new Concurrent(tasks, "资源库1.4登录>查询>收藏>取消收藏链路压测").start()

allOver()
}

private static class FunTester extends ThreadLimitTimesCount<Integer> {

OkayBase base

def mirro

def clazz

FunTester(Integer integer, int times) {
super(integer, times, null)
}

@Override
void before() {
super.before()
base = getBase(t)
mirro = new Mirro(base)
clazz = new OKClass(base)
}

@Override
protected void doing() throws Exception {

def klist = mirro.getKList()
def karray = klist.getJSONArray("data")
K ks
karray.each {
JSONObject parse = JSON.parse(JSON.toJSONString(it))
if (ks == null) {
def level = parse.getIntValue("node_level")
def type = parse.getIntValue("ktype")
def id = parse.getIntValue("id")
ks = new K(id, type, level)
}
}
JSONObject response = clazz.recommend(, ks.type, ks.level)
def minis = []
int i = 0
response.getJSONArray("data").each {
if (i++ < 2) {
JSONObject parse = JSON.parse(JSON.toJSONString(it))
int value = parse.getIntValue("minicourse_id")
minis << value
}
}
clazz.unCollect(random(minis))

mirro.getMiniCourseListV3(, ks.type, 0, ks.level)
}
}

private static class K extends AbstractBean {

int id

int type

int level

K(int id, int type, int level) {
this.id = id
this.type = type
this.level = level
}
}

}

Part4用例传输

3上传用例

这个其实没啥可说的,就是写好用例,打包编译,然后重启服务即可。

多说一嘴,传说中的​​Java​​热更新和​​ClassLoader​​加载外部类和jar文件等高端技术,各位可以自行选择探索。

4分配用例

采取和用例方案设想(一)中相同的方式,应该所有的设想中,分配用例思路都是一样的,不同的就是任务类对象的设计和实现。

Part5用例执行

如通本文开始实现​​Demo​​中所写,就是执行测试任务中具体用例的方法了。执行类的代码如下:

package com.funtester.frame.execute;

import com.alibaba.fastjson.JSON;
import com.funtester.base.exception.FailException;
import com.funtester.frame.SourceCode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

public class ExecuteSource extends SourceCode {

private static Logger logger = LogManager.getLogger(ExecuteSource.class);

/**
* 执行方法
* <p>防止编译报错,用list绕一圈</p>
*
* @param params
*/
public static Object executeMethod(List<String> params) {
Object[] objects = params.subList(1, params.size()).toArray();
return executeMethod(params.get(0), objects);
}

/**
* 执行方法
* <p>防止编译报错,用list绕一圈</p>
*
* @param params
*/
public static Object executeMethod(String[] params) {
return executeMethod(Arrays.asList(params));
}

/**
* 执行具体的某一个方法,提供内部方法调用
* <p>重载方法如果参数是基础数据类型会报错</p>
*
* @param path
* @param paramsTpey
*/
public static Object executeMethod(String path, Object... paramsTpey) {
int length = paramsTpey.length;
if (length % 2 == 1) FailException.fail("参数个数错误,应该是偶数");
String className = path.substring(0, path.lastIndexOf("."));
String methodname = path.substring(className.length() + 1);
Class<?> c = null;
Object object = null;
try {
c = Class.forName(className);
object = c.newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
logger.warn("创建实例对象时错误:{}", className, e);
}
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
if (!method.getName().equalsIgnoreCase(methodname)) continue;
try {
Class[] classs = new Class[length / 2];
for (int i = 0; i < paramsTpey.length; i +=2) {
classs[i / 2] = Class.forName(paramsTpey[i].toString());//此处基础数据类型的参数会导致报错,但不影响下面的调用
}
method = c.getMethod(method.getName(), classs);
} catch (NoSuchMethodException | ClassNotFoundException e) {
logger.warn("方法属性处理错误!");
}
try {
Object[] ps = new Object[length / 2];
for (int i = 1; i < paramsTpey.length; i +=2) {
String name = paramsTpey[i - 1].toString();
String param = paramsTpey[i].toString();
Object p = param;
if (name.contains("Integer")) {
p = Integer.parseInt(param);
} else if (name.contains("JSON")) {
p = JSON.parseObject(param);
}
ps[i / 2] = p;
}
method.invoke(object, ps);
} catch (IllegalAccessException | InvocationTargetException e) {
logger.warn("反射执行方法失败:{}", path, e);
}
break;
}
return null;
}

}

有一些暂时无用的方法我已删除,有兴趣的可以去仓库看看。


FunTester,腾讯云年度作者​、Boss直聘签约作者​,GDevOps官方合作媒体,非著名测试开发,欢迎关注。


  • FunTester测试框架架构图初探
  • 性能测试软启动初探
  • 多项目登录互踢测试用例
  • 压测中测量异步写入接口的延迟
  • 分布式性能测试框架用例方案设想(一)
  • 敏捷团队的自动化测试【译】
  • 自动化测试框架的完整指南【译】
  • moco框架接口命中率统计实践
  • 无脚本测试
  • 好书推荐《Java性能权威指南》

分布式性能测试框架用例方案设想(二)_用例