异步验证
场景分析
!!!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}