在通用测试设置中使用反应式扩展的创造性方法

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

我不知道自图形用户界面交互以来事件是否成为软件工程的一部分,但可以肯定的是,它们是一种非常方便的建模方式。随着越来越多的互连系统,异步事件管理已成为需要解决的重要问题。随着函数式编程的兴起,这催生了诸如 RxJava 之类的库。但是,对处理事件流的问题建模不应仅限于系统事件处理。它还可以以多种不同的方式用于测试。

测试设置的一个常见用例是启动程序,例如模拟服务器等外部依赖项。在这种情况下,我们需要等到程序成功启动。相反,一旦外部程序启动失败,测试就应该停止。如果程序有 Java API,那很容易。然而,这种情况很少见,通常使用更基本的 API,例如 ProcessBuilder Runtime.getRuntime().exec()


 ProcessBuilder builder;

@BeforeMethod protected void setUp() throws IOException { builder = new ProcessBuilder().command("script.sh"); process = builder.start(); }

@AfterMethod protected void tearDown() { process.destroy(); }

处理这个问题的传统方法是在启动后放置一个大的 Thread.sleep() 。它不仅依赖于系统,因为启动时间因系统而异,而且它没有解决启动失败的情况。在后一种情况下,浪费了宝贵的计算时间和手动重新启动时间。存在更好的解决方案,但它们在某种(或高度)复杂度下涉及很多代码行。如果我们可以有一种简单可靠的方法来启动程序并根据输出继续设置或使测试失败,那不是很好吗? Rx 来救援!

第一步是围绕启动进程的输入流创建一个 Observable


 ProcessBuilder builder;

@BeforeMethod protected void setUp() throws IOException { builder = new ProcessBuilder().command("script.sh"); process = builder.start(); }

@AfterMethod protected void tearDown() { process.destroy(); }

在上面的片段中:

  • 每次进程写入输出时,都会发送下 一个 事件
  • 当没有更多输出时,发送一个 完成 事件
  • 最后,如果发生异常,则为 错误 事件

根据 Rx 定义,最后两个事件中的任何一个都标志着序列的结束。

一旦创建了可观察对象,就只需要观察它的事件。可以使用以下代码片段收听发出单个事件的简单脚本:


 ProcessBuilder builder;

@BeforeMethod protected void setUp() throws IOException { builder = new ProcessBuilder().command("script.sh"); process = builder.start(); }

@AfterMethod protected void tearDown() { process.destroy(); }

这里感兴趣的是将 Observable 实例包装在 BlockingObservable 中。前者可以组合在一起,而后者增加了管理事件的方法。此时, first() 方法将监听第一个(也是单个)事件。

对于发出由单个结束事件终止的随机数量的常规事件的更复杂的脚本,代码片段可以是:


 ProcessBuilder builder;

@BeforeMethod protected void setUp() throws IOException { builder = new ProcessBuilder().command("script.sh"); process = builder.start(); }

@AfterMethod protected void tearDown() { process.destroy(); }

在这种情况下,无论有多少 常规 事件, filter() 方法都提供了监听我们感兴趣的唯一事件的方法。

然而,以前的案例并不能反映现实。大多数时候,设置脚本应该在测试之前启动并与测试并行运行 ,即 永远不会发送结束事件——至少在测试完成之前是这样。在这种情况下,涉及到一些线程。 Rx 让它很容易处理:


 ProcessBuilder builder;

@BeforeMethod protected void setUp() throws IOException { builder = new ProcessBuilder().command("script.sh"); process = builder.start(); }

@AfterMethod protected void tearDown() { process.destroy(); }

这里有一个简单的区别:由于 subscribeOn() 方法,订阅者将监听一个新线程。或者,可以使用 observeOn() 方法在另一个线程上发出事件。请注意,我将 filter() 方法替换为 take() 以假装只对前 5 个事件感兴趣。

至此,测试设置完成。测试愉快!