单元测试中异步任务的处理策略

📝 面试求职: 「面试试题小程序」 ,内容涵盖 测试基础、Linux操作系统、MySQL数据库、Web功能测试、接口测试、APPium移动端测试、Python知识、Selenium自动化测试相关、性能测试、性能测试、计算机网络知识、Jmeter、HR面试,命中率杠杠的。(大家刷起来…)

📝 职场经验干货:

软件测试工程师简历上如何编写个人信息(一周8个面试)

软件测试工程师简历上如何编写专业技能(一周8个面试)

软件测试工程师简历上如何编写项目经验(一周8个面试)

软件测试工程师简历上如何编写个人荣誉(一周8个面试)

软件测试行情分享(这些都不了解就别贸然冲了.)

软件测试面试重点,搞清楚这些轻松拿到年薪30W+

软件测试面试刷题小程序免费使用(永久使用)


在 Java 中使用 ExecutorService 管理异步任务时,单元测试需要验证任务的执行状态、顺序或结果。然而,依赖 Thread.sleep() 等待任务完成不仅不可靠,还会导致测试效率低下或间歇性失败。本文结合小八超市的 WebSocket 商品查询场景,介绍几种替代 Thread.sleep() 的优雅测试策略,确保测试的稳定性与效率。这些策略特别适用于分布式系统或高并发场景下的性能测试。

ExecutorService 测试挑战

ExecutorService 是 Java 提供的线程池框架,用于高效管理异步任务。在小八超市的 WebSocket 商品查询系统中,ExecutorService 可用于并行发送查询请求或处理推送消息。单元测试的目标是验证任务是否正确执行、结果是否符合预期,以及并发逻辑是否可靠。

测试挑战:

  • 非确定性Thread.sleep() 的固定等待时间可能不足以覆盖任务执行,或过长导致测试时间浪费。例如,测试 WebSocket 请求可能因网络延迟而失败。

  • 性能低下:固定休眠时间无法适应任务执行时间的动态变化,影响测试效率。

  • 并发复杂性:多线程环境下,需确保任务完成顺序和结果正确性,避免数据竞争。

以下介绍三种更可靠的替代方案:Future.get()、 CountDownLatch 和 shutdown()/awaitTermination()

Future

Future.get() 阻塞主线程直到任务完成,返回任务结果或抛出异常,适合需要验证返回值的场景。

以下代码模拟小八超市的商品查询任务,验证 WebSocket 客户端是否正确返回商品价格:

package org.funtester.performance.books.chapter05.section7;

import org.funtester.performance.books.chapter05.section2.JavaWebSocketClient;
import org.junit.jupiter.api.Test;
import com.alibaba.fastjson.JSONObject;

import java.net.URI;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

importstatic org.junit.jupiter.api.Assertions.assertEquals;

/**
 * FunTester 使用 Future.get() 测试 WebSocket 客户端
 */
publicclass WebSocketFutureTest {

    // 使用 JUnit 的 @Test 注解标记测试方法
    @Test
    public void testWebSocketQuery() throws Exception {
        // 创建单线程线程池
        ExecutorService executor = Executors.newSingleThreadExecutor();
        // 构造 WebSocket 服务器的 URI
        URI uri = new URI("ws://localhost:12345/websocket/FunTester");
        // 创建 WebSocket 客户端实例
        JavaWebSocketClient client = new JavaWebSocketClient(uri);
        // 连接到 WebSocket 服务器
        client.connect();

        // 提交异步任务,发送请求并等待响应
        Future<JSONObject> future = executor.submit(() -> {
            JSONObject params = new JSONObject();
            params.put("name", "西瓜");
            params.put("index", 1);
            // 发送参数到服务器
            client.send(params);

            // 模拟异步等待响应,轮询检查消息
            for (int i = 0; i < 10; i++) {
                if (client.getLastMessage() != null) {
                    // 收到消息后解析为 JSONObject 并返回
                    return JSONObject.parseObject(client.getLastMessage());
                }
                Thread.sleep(100); // 每 100ms 轮询一次
            }
            returnnull;
        });

        // 阻塞直到异步任务完成,获取结果
        JSONObject result = future.get();
        // 断言返回结果的字段值
        assertEquals("西瓜", result.getString("Name"));
        assertEquals(45, result.getInteger("Price"));
        // 关闭 WebSocket 客户端
        client.close();
        // 关闭线程池
        executor.shutdown();
    }
}

代码分析:

  • 适用场景:适合验证任务返回值的场景,如小八超市的商品查询响应。

  • 优势Future.get() 确保任务完成,避免手动设置休眠时间。

  • 注意点:需处理可能的 InterruptedException 和 ExecutionException。在小八超市场景中,可结合 MockServer 模拟响应,验证客户端对不同价格的处理。

  • 局限性:若任务长时间未完成,get() 会阻塞测试线程,可能影响效率。

CountDownLatch

CountDownLatch 是一种同步工具,允许主线程等待指定数量的任务完成,适合多任务协作场景。

以下代码测试小八超市的多个 WebSocket 客户端并发查询:

package org.funtester.performance.books.chapter05.section7;

import org.funtester.performance.books.chapter05.section2.JavaWebSocketClient;
import org.junit.jupiter.api.Test;
import com.alibaba.fastjson.JSONObject;

import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

importstatic org.junit.jupiter.api.Assertions.assertTrue;

/**
 * FunTester 使用 CountDownLatch 测试 WebSocket 客户端
 */
@Test
public void testMultipleWebSocketQueries() throws Exception {
    // 创建一个固定大小为2的线程池
    ExecutorService executor = Executors.newFixedThreadPool(2);
    // 创建一个倒计时锁存器,计数为2,用于等待2个任务完成
    CountDownLatch latch = new CountDownLatch(2); // 等待 2 个任务
    // WebSocket服务器的URI
    URI uri = new URI("ws://localhost:12345/websocket/FunTester");
    // 创建第一个WebSocket客户端,并重写onMessage方法
    JavaWebSocketClient client1 = new JavaWebSocketClient(uri) {
        @Override
        public void onMessage(String s) {
            super.onMessage(s);
            // 收到响应后计数减1
            latch.countDown(); 
        }
    };
    // 创建第二个WebSocket客户端,并重写onMessage方法
    JavaWebSocketClient client2 = new JavaWebSocketClient(uri) {
        @Override
        public void onMessage(String s) {
            super.onMessage(s);
            // 收到响应后计数减1
            latch.countDown();
        }
    };
    // 连接WebSocket服务器
    client1.connect();
    client2.connect();

    // 在线程池中提交第一个任务,发送消息
    executor.submit(() -> {
        JSONObject params = new JSONObject();
        params.put("name", "西瓜");
        params.put("index", 1);
        client1.send(params);
    });
    // 在线程池中提交第二个任务,发送消息
    executor.submit(() -> {
        JSONObject params = new JSONObject();
        params.put("name", "苹果");
        params.put("index", 2);
        client2.send(params);
    });

    // 等待2分钟,直到latch计数为0,即两个任务都完成
    assertTrue(latch.await(2, TimeUnit.MINUTES)); // 等待任务完成
    // 关闭WebSocket客户端
    client1.close();
    client2.close();
    // 关闭线程池
    executor.shutdown();
}

Case 代码解读:

  • 适用场景:适合多任务协作测试,如小八超市的多个用户并发查询商品价格。

  • 优势CountDownLatch 精确控制任务完成时机,支持超时机制(await 指定时间),避免无限等待。

  • 注意点:需在任务中调用 countDown(),确保逻辑正确。在小八超市场景中,可验证多个客户端是否都能收到正确响应。

  • 局限性:需手动维护计数器,逻辑较复杂时易出错。

shutdown + awaitTermination

ExecutorService.shutdown() 和 awaitTermination() 组合适合验证所有任务是否完成,无需获取具体返回值。

以下代码测试小八超市的 WebSocket 客户端批量查询:

package org.funtester.performance.books.chapter05.section7;

import org.funtester.performance.books.chapter05.section2.JavaWebSocketClient;
import org.junit.jupiter.api.Test;
import com.alibaba.fastjson.JSONObject;

import java.net.URI;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

importstatic org.junit.jupiter.api.Assertions.assertTrue;

/**
 * FunTester 使用 shutdown() + awaitTermination() 测试 WebSocket 客户端
 */
publicclass WebSocketShutdownTest {

// 批量测试 WebSocket 查询
@Test
public void testBatchWebSocketQueries() throws Exception {
     // 创建一个固定大小为2的线程池
     ExecutorService executor = Executors.newFixedThreadPool(2);
     // WebSocket 服务器地址
     URI uri = new URI("ws://localhost:12345/websocket/FunTester");
     // 创建 WebSocket 客户端
     JavaWebSocketClient client = new JavaWebSocketClient(uri);
     client.connect();

     // 向 WebSocket 发送两组不同参数的请求
     for (int i = 1; i <= 2; i++) {
         int index = i;
         executor.submit(() -> {
             JSONObject params = new JSONObject();
             params.put("name", index == 1 ? "西瓜" : "苹果");
             params.put("index", index);
             client.send(params);
         });
     }

     // 关闭线程池,等待所有任务完成
     executor.shutdown();
     assertTrue(executor.awaitTermination(2, TimeUnit.MINUTES)); // 等待所有任务完成
     client.close();
 }
}

代码分析:

  • 适用场景:适合验证任务是否全部完成,如小八超市的批量查询请求。

  • 优势:简单易用,无需额外同步工具,适合任务数量较多但无需具体结果的场景。

  • 注意点awaitTermination 需要在 shutdown 后调用,且需设置合理超时时间。在小八超市场景中,可确保所有查询请求都发送完成。

  • 局限性:无法直接获取任务结果,适合状态验证而非结果验证。

自定义 Runnable

以下是通用的 FunTesterRunnable 类,模拟耗时任务,用于测试验证:

package org.funtester.performance.books.chapter05.section7;

publicclass FunTesterRunnable implements Runnable {
    privatefinallong start, end;
    privatelong result;

    public FunTesterRunnable(long start, long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public void run() {
        result = 0;
        for (long i = start; i <= end; i++) {
            result += i;
        }
        System.out.println("FunTester: 计算完成,结果: " + result);
    }

    public long getResult() {
        return result;
    }
}

在 WebSocket 测试中,可将 FunTesterRunnable 替换为实际的查询任务。例如,任务可发送商品查询请求并验证响应内容,结合 MockServer 模拟服务端响应(如正常、库存不足、超时)。

总结

对基于 ExecutorService 的并发代码进行单元测试时,需要谨慎设计同步机制,以确保测试具有可靠性和确定性。依赖 Thread.sleep() 会导致测试不稳定并降低执行效率,应尽量避免。在本文中,我们展示了如何使用 CountDownLatch、Future 和 shutdown / awaitTermination() 等同步工具,替代传统的休眠方式,从而更加高效、稳健地测试并发逻辑。

最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】

​​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值