如何让Java JUnit测试运行指定时长后终止且不标记失败
问题:如何在多线程循环运行指定时长后执行断言,避免Timeout导致测试失败
我有一个启动多个运行while(true)循环线程的类,想在测试运行一定时长后对其状态执行Assert断言,但现有的Timeout功能会导致测试失败,无法满足需求。
示例代码:
class RunProgram { private DataClass dataClass = new DataClass(); private Thread1 thread1 = new Thread1(dataClass); void startThis() { ExecutorService pool = Executors.newFixedThreadPool(5); try { pool.execute(thread1);//Thread 1 //... more threads with while loops runMainThread(dataClass); } finally { pool.shutdown(); } } void runMainThread(DataClass data1){ while(true){ dataClass.setInternalDataInt(20); //do stuff } } public Thread1 getThread1(){ return this.thread1; } } class Thread1 implements Runnable{ private DataClass dataClass; Thread1(DataClass dataClass){ this.dataClass = dataClass; } public void run() { dataClass.setInternalDataInt(10); while (true) { //dostuff } } public DataClass getDataClass(){ return dataClass; } public void setDataClass(DataClass dataClass){ this.dataClass = dataClass; } } class DataClass { private int internalDataInt; public int getInternalDataInt(){ return this.internalDataInt; } public void setInternalDataInt(int internalDataInt){ this.internalDataInt = internalDataInt; } } class Tests{ @Test public void stateOfThread1() { RunProgram runProgram = new RunProgram(); runProgram.startThis(); //Run above for 100 millisecond and then end Assertions.assertEquals(runProgram.getThread1().getDataClass().getInternalDataInt(), 20); } }
解决方案
方法1:修改线程支持中断,手动控制测试时长
核心是让循环线程响应中断信号,测试主线程等待指定时间后主动终止后台线程,再执行断言。
- 修改循环线程逻辑:将
while(true)改为while(!Thread.currentThread().isInterrupted()),让线程能感知中断 - 调整RunProgram类:把
runMainThread提交到线程池(避免阻塞测试主线程),新增终止线程的方法 - 修改测试类:启动程序后休眠指定时长,终止线程后执行断言
修改后的代码:
// 修改Thread1类 class Thread1 implements Runnable{ private DataClass dataClass; Thread1(DataClass dataClass){ this.dataClass = dataClass; } public void run() { dataClass.setInternalDataInt(10); // 改为响应中断的循环 while (!Thread.currentThread().isInterrupted()) { //dostuff } } // 原有getter/setter不变 } // 修改RunProgram类 class RunProgram { private DataClass dataClass = new DataClass(); private Thread1 thread1 = new Thread1(dataClass); private ExecutorService pool; void startThis() { pool = Executors.newFixedThreadPool(5); try { pool.execute(thread1); // 把runMainThread提交到线程池,避免阻塞主线程 pool.execute(() -> runMainThread(dataClass)); } catch (Exception e) { pool.shutdownNow(); } } void runMainThread(DataClass data1){ // 改为响应中断的循环 while(!Thread.currentThread().isInterrupted()){ dataClass.setInternalDataInt(20); //do stuff } } public Thread1 getThread1(){ return this.thread1; } // 新增:终止所有线程 public void stopAllThreads() { if (pool != null) { pool.shutdownNow(); } } } // 修改测试类 class Tests{ @Test public void stateOfThread1() throws InterruptedException { RunProgram runProgram = new RunProgram(); runProgram.startThis(); // 等待100毫秒 Thread.sleep(100); // 主动终止后台线程 runProgram.stopAllThreads(); // 执行断言 Assertions.assertEquals(runProgram.getThread1().getDataClass().getInternalDataInt(), 20); } }
方法2:用定时任务触发断言(无需修改原线程逻辑)
如果不想改动原线程代码,可以用定时任务在指定时间后执行断言,同时强制终止测试进程:
class Tests{ @Test public void stateOfThread1() throws InterruptedException { RunProgram runProgram = new RunProgram(); CountDownLatch latch = new CountDownLatch(1); // 单独线程启动程序,避免阻塞测试主线程 new Thread(runProgram::startThis).start(); // 100毫秒后执行断言并结束测试 ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.schedule(() -> { try { Assertions.assertEquals(runProgram.getThread1().getDataClass().getInternalDataInt(), 20); } finally { latch.countDown(); // 强制终止所有线程 System.exit(0); } }, 100, TimeUnit.MILLISECONDS); latch.await(); scheduler.shutdown(); } }
关键注意事项
- 线程可见性:
DataClass的internalDataInt字段需要加volatile关键字,或者改用AtomicInteger,避免断言时读取到缓存的旧值:class DataClass { private volatile int internalDataInt; // 原有getter/setter不变 } - 阻塞操作的中断响应:如果线程循环内有
Thread.sleep()等阻塞方法,要确保这些方法能抛出InterruptedException,否则shutdownNow()无法终止线程。
内容的提问来源于stack exchange,提问作者jlaufer
相关产品推荐
相关产品推荐

