项目概况

异步验证

2026-01-292 min read未分类
场景分析

!!!danger 请听题 对于下面这段代码你觉得单测能通过吗? !!!

异步场景

java
1 @Test 2 public void test() { 3 ExecutorService executorService = Executors.newFixedThreadPool(3); 4 executorService.submit(new Runnable() { 5 @SneakyThrows 6 @Override 7 public void run() { 8 Thread.sleep(5000); 9 Object obj = null; 10 System.out.println(obj.toString()); 11 } 12 }); 13 System.out.println("单侧结束"); 14 }

一、常用解决方案

1.1 white解决简单暴力

white解决

java
1 @Test 2 public void test() { 3 ExecutorService executorService = Executors.newFixedThreadPool(3); 4 executorService.submit(new Runnable() { 5 @SneakyThrows 6 @Override 7 public void run() { 8 Thread.sleep(5000); 9 Object obj = null; 10 System.out.println(obj.toString()); 11 } 12 }); 13 System.out.println("单侧结束"); 14 white(true); 15 }

1.2 LockSupport最大时间限制

LockSupport.parkNanos()线程挂起

java
1 @Test 2 public void test() { 3 ExecutorService executorService = Executors.newFixedThreadPool(3); 4 executorService.submit(new Runnable() { 5 @SneakyThrows 6 @Override 7 public void run() { 8 Thread.sleep(5000); 9 Object obj = null; 10 System.out.println(obj.toString()); 11 } 12 }); 13 System.out.println("单侧结束"); 14 // 挂起指定时间 15 LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(6)); 16 }

二、基于上面两种配合JUnit定制

2.1 使用演示

📢 注意这里的 @Timed 原生是不具备这个能力的,要基于JUnit进行扩展。

@Timed 灵活控制时间

java
1 @Test 2 @Timed(millis = 5000) 3 public void test() { 4 ExecutorService executorService = Executors.newFixedThreadPool(3); 5 executorService.submit(new Runnable() { 6 @SneakyThrows 7 @Override 8 public void run() { 9 Thread.sleep(5000); 10 System.out.println("任务执行结束"); 11 } 12 }); 13 System.out.println("单侧结束"); 14 }

2.2 扩展实现

同样是基于LockSupport线程挂起方案,类似于切面解决。

扩展TestExecutionListener

java
1 2 private Map<String, Long> timedMap = new HashMap<>(); 3 4 private Map<String, Long> beforeTestCostMap = new HashMap<>(); 5 6 @Override 7 public void beforeTestMethod(TestContext testContext) throws Exception { 8 String key = testContext.getTestMethod().getName(); 9 beforeTestCostMap.put(key, System.currentTimeMillis()); 10 Timed timedA = AnnotationUtils.getAnnotation(testContext.getTestMethod(), Timed.class); 11 if (Objects.nonNull(timedA)) { 12 timedMap.put(testContext.getTestMethod().getName(), timedA.millis()); 13 } 14 Method testMethod = testContext.getTestMethod(); 15 printActiveProfile(testContext); 16 checkTransactional(testContext); 17 TestConsole.colorPrintln(AnsiColor.BLUE, "西魏陶渊明发起了一个单侧用例: {}#{}", testContext.getTestClass(), testMethod.getName()); 18 } 19 20 @Override 21 public void afterTestMethod(TestContext testContext) throws Exception { 22 String key = testContext.getTestMethod().getName(); 23 Long afterTestCost = System.currentTimeMillis(); 24 Long beforeTestCost = beforeTestCostMap.get(key); 25 long timed = timedMap.get(key); 26 // 如果耗时已经大于指定的时间了,就直接过 27 if ((timed <= 0) || afterTestCost - beforeTestCost > timed) { 28 Throwable testException = testContext.getTestException(); 29 if (Objects.nonNull(testException)) { 30 TestConsole.colorPrintln(AnsiColor.BRIGHT_RED, "测试用例执行失败了,快检查检查吧。🚒"); 31 } else { 32 TestConsole.colorPrintln("用例执行成功。💪"); 33 } 34 } else { 35 // 如果不够,就要挂起指定时间。(减去1000毫秒,给Timed预留的时间) 36 long nanos = TimeUnit.MILLISECONDS.toNanos(timed - (afterTestCost - beforeTestCost) - 1000); 37 // 主线程挂起,等待异步执行 38 System.err.printf("Timed任务挂起通知: 主线程挂起%d s,等待异步执行%n", TimeUnit.NANOSECONDS.toSeconds(nanos)); 39 LockSupport.parkNanos(nanos); 40 } 41 42 }

2.3 引导类配置

  • @TestExecutionListeners 注意声明添加模式是合并(默认是替换)
java
1@Slf4j 2@ActiveProfiles({"local"}) 3@ContextConfiguration(initializers = {BeanLazyApplicationContextInitializer.class}) 4// 使用Spring容器引导 5@RunWith(SpringRunner.class) 6// 合并模式下,增加测试执行监听器 7@TestExecutionListeners(value = PmsSentryTestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) 8// 默认就是回滚,不用加@Rollback,如果全局不想回滚就在这个吧@Rollback(false),如果某个单测不想回滚,就放到单侧类上 9@Transactional 10@SpringBootTest(classes = {CenterProviderApplication.class}) // 指定启动类 11public class BaseApplicationTest { 12}