Building a Distributed Load‑Testing Framework: Gradle Multi‑Module & Maven Tips

This article details the final updates to the DCS_FunTester distributed testing framework, covering Gradle multi‑module configuration, dependency handling, result aggregation, task dispatch, health‑check endpoints, and registration optimizations with practical code examples in Java and Maven.

FunTester
FunTester
FunTester
Building a Distributed Load‑Testing Framework: Gradle Multi‑Module & Maven Tips

After two rounds of updates, the core features of the DCS_FunTester framework are stable, and no further functional updates are planned.

Part 1 – Gradle Multi‑Module

The project originally used two separate modules named master and slave. To simplify management, they were merged into a single Gradle multi‑module project, revealing several pitfalls.

1. Adding a Dependency on Another Module

A working solution is to declare the dependency in build.gradle as:

dependencies {
    implementation project(':slave')
}

Typical tutorials often miss this configuration, and the correct template for multi‑module Gradle projects was discovered through trial and error.

2. Sub‑module Dependency Configuration

Attempts to configure sub‑module dependencies in the parent build.gradle using a subprojects block failed, likely due to misuse of the compile configuration or local JAR references.

3. settings.gradle

The essential settings.gradle content is:

rootProject.name = 'dcs_funtester'
include 'slave'
include 'master'

Part 2 – Result Collection

Test results are aggregated on the master node. After a slave node finishes executing a test case, it synchronizes its results to the master node, which then handles storage, merging, and calculation.

Example of a request execution on a slave node:

@Async
@Override
public void runRequest(HttpRequest request) {
    BaseRequest r = request.getRequest();
    HttpRequestBase re = FunRequest.initFromJson(r.toJson()).getRequest();
    Integer times = request.getTimes();
    String mode = request.getMode();
    Integer thread = request.getThread();
    Integer runup = request.getRunup();
    String desc = request.getDesc();
    if (mode.equalsIgnoreCase("ftt")) {
        Constant.RUNUP_TIME = runup;
        RequestThreadTimes task = new RequestThreadTimes(re, times);
        Concurrent concurrent = new Concurrent(task, thread, desc);
        PerformanceResultBean resultBean = concurrent.start();
        SlaveManager.updateResult(resultBean, request.getMark());
    }
}

The corresponding master controller endpoint receives the aggregated result:

@ApiOperation(value = "更新测试结果")
@ApiImplicitParam(name = "bean", value = "测试结果对象", dataTypeClass = PerformanceResultBean.class)
@PostMapping(value = "/upresult/{mark}")
public Result updateResult(@PathVariable(value = "mark") int mark, @RequestBody PerformanceResultBean bean) {
    NodeData.addResult(mark, bean);
    return Result.success();
}

Result storage is kept in memory within the JVM for now, without persistence, as the design may evolve to use server‑side statistics.

Part 3 – Task Distribution

When the master node receives a task, it compares the required node count with available nodes. If sufficient, it dispatches the task to each node; on any failure, it rolls back already started nodes and reports failure.

@Override
int runRequest(HttpRequest request) {
    def num = request.getMark();
    def hosts = NodeData.getRunHost(num);
    def mark = SourceCode.getMark();
    request.setMark(mark);
    try {
        hosts.each {
            def re = MasterManager.runRequest(it, request);
            if (!re) FailException.fail();
            NodeData.addTask(it, mark);
        }
    } catch (FailException e) {
        hosts.each { f -> MasterManager.stop(f) };
        FailException.fail("多节点执行失败!");
    }
    return mark;
}

Part 4 – Health‑Check Interface

A temporary health‑check endpoint was added to slave nodes so that the master can periodically verify node liveness, reducing reliance on passive reports.

@ApiOperation(value = "节点状态,是否存活")
@GetMapping(value = "/alive")
public Result alive() {
    return Result.success();
}

The master side checks liveness via HTTP:

static boolean alive(String host) {
    try {
        String url = SlaveApi.ALIVE;
        return isRight(getGetResponse(host, url, null));
    } catch (Exception e) {
        logger.warn("节点: {}探活失败!", host);
        return false;
    }
}

Part 5 – Registration Optimization

To prevent a slave node that can reach the master but not vice‑versa from being registered, the registration process now includes a health‑check step.

@ApiOperation(value = "注册接口")
@PostMapping(value = "/register")
public Result register(@Valid @RequestBody RegisterBean bean) {
    def url = bean.getUrl();
    def stop = MasterManager.alive(url);
    if (!stop) FailException.fail("注册失败!");
    NodeData.register(url, false);
    return Result.success();
}

The overall development of DCS_FunTester is now complete, with basic functionality in place; future work will integrate a service‑discovery component such as Nacos for more robust node management.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaGradlemavenLoad TestingDistributed TestingFramework
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

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.