Operations 8 min read

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.

FunTester
FunTester
FunTester
Multi‑Interface Load Testing for Collect/Uncollect Feature Using FunTester

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.

backendJavaload testingMultithreadingGroovyAPI performanceFunTester
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.