Multi‑Interface Load Testing for Collect/Uncollect Feature Using FunTester
This article describes how to implement a multi‑interface performance test for the collect and uncollect operations of a teacher’s resource list, using Groovy scripts, a custom FunTester framework, multithreaded request generation, request‑id tracking, and response recording to produce QPS and RT metrics.
The article revisits a previous single‑request timing record and introduces a new requirement to perform load testing on a chain of multiple API calls for the "collect" and "uncollect" actions on a teacher's resource list, aiming to improve the compatibility of the FunTester framework.
Business Requirement
Teachers need to be able to add or remove resources from their favorites list on the homepage.
API Parameters
Two endpoints are provided: collect and unCollect . The Java methods are shown below.
/**
* 收藏OK智课
* @param minicourse_id
* @param ktype 0-机构,1-老师
* @return
*/
public JSONObject collect(int minicourse_id = 43089, int ktype = 0, int grade_id = 12) {
String url = OKClassApi.COLLECT;
def params = getParams();
params.put("org_id", 80);
params.put("org_type", 1);
params.put("minicourse_id", minicourse_id);
params.put("kid_route", [82]);
params.put("ktype", ktype);
params.put("grade_id", grade_id);
params.put("link_source", 1); //0-教师空间,1-教师机
def response = getPostResponse(url, params);
output(response);
return response;
}
/**
* 取消收藏
* @param minicourse_id
* @param ktype
* @param grade_id
* @return
*/
public JSONObject unCollect(int minicourse_id = 43089, int ktype = 0) {
String url = OKClassApi.UNCOLLECT;
def params = getParams();
params.put("minicourse_id", minicourse_id);
params.put("kid_route", [82]);
params.put("ktype", ktype);
def response = getPostResponse(url, params);
output(response);
return response;
}Test Plan
Each thread creates a unique user object and performs a "collect" followed by an "uncollect" request, treating each loop as a single request. QPS, RT and other metrics are calculated and visualized.
Test Script
The script is written in Groovy, which is syntactically close to Java, and uses an AtomicInteger to assign distinct user objects to threads. The core class is shown below.
package com.okayqa.composer.performance.master1_0
import com.fun.base.constaint.ThreadLimitTimesCount
import com.fun.frame.execute.Concurrent
import com.fun.frame.httpclient.ClientManage
import com.fun.utils.ArgsUtil
import com.okayqa.common.Common
import com.okayqa.composer.base.OkayBase
import com.okayqa.composer.function.OKClass
import java.util.concurrent.atomic.AtomicInteger
class BothCollect extends OkayBase {
static AtomicInteger u = new AtomicInteger(0)
static int times
static int thread
public static void main(String[] args) {
ClientManage.init(5, 1, 0, "", 0)
def util = new ArgsUtil(args)
thread = util.getIntOrdefault(0, 200)
times = util.getIntOrdefault(1, 100)
def funs = []
thread.times { funs << new Fun() }
new Concurrent(funs, "收藏和取消收藏").start()
allOver()
}
static int getTimes() { return times }
static class Fun extends ThreadLimitTimesCount {
OkayBase okayBase = getBase(u.getAndIncrement())
OKClass driver = new OKClass(okayBase)
public Fun() { super(null, getTimes(), null) }
@Override
protected void doing() throws Exception {
def collect = driver.collect()
if (collect.getJSONObject("meta").getIntValue("ecode") != 0) fail("collect request error!")
def unCollect = driver.unCollect()
if (unCollect.getJSONObject("meta").getIntValue("ecode") != 0) fail("uncollect request error!")
}
}
}Recording Implementation
The Base class is extended with a private HttpRequestBase last; field. The getResponse method records the request before sending, and recordRequest stores the last request object.
@Override
public JSONObject getResponse(HttpRequestBase httpRequestBase) {
setHeaders(httpRequestBase);
recordRequest(httpRequestBase);
JSONObject response = FanLibrary.getHttpResponse(httpRequestBase);
handleResponseHeader(response);
return response;
}
@Override
public void recordRequest(HttpRequestBase base) {
this.last = base;
}
@Override
public HttpRequestBase getRequest() {
return last == null ? FanLibrary.getLastRequest() : last;
}
public String getLastRequestId() {
HttpRequestBase httpRequestBase = getLast();
return httpRequestBase.getFirstHeader(Common.REQUEST_ID.getName()).getValue();
}Since Base objects are created in a single thread, thread‑safety concerns are minimal.
Additional Resources
Links to a series of video tutorials covering the FunTester framework, HTTP request handling, header/cookie management, and multithreaded load‑testing patterns are provided, along with Git repository URLs on Gitee and GitHub.
FunTester
10k followers, 1k articles | completely useless
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.