在自动化测试中使用 JFR 事件流 API - Sip of Java
Billy Korando 于 2022 年 10 月 31 日
JDK Flight Recorder (JFR) 是一种性能分析和诊断工具,长期以来一直是 JDK 的一部分。在大部分历史中,JFR 只能用于部署的应用程序,以提供对其性能的洞察。随着 JDK 14(JEP 349)的发布,添加了 JFR 事件流 API,它提供了一种实时查看 Java 应用程序内部发生情况的方法。本文将探讨如何在自动化测试中利用 JFR 事件流 API。
在自动化测试中使用事件流 API
以下是使用 JFR 事件流在自动化测试中的完整代码示例。我们将在下面逐步介绍此代码示例的关键部分。
import jdk.jfr.Event;
import jdk.jfr.Name;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordingStream;
public class TestSampleApplication {
@Name("TestEvent")
class TestEvent extends Event {
}
@Test
public void testDoStuff() throws Exception {
SampleApplication sampleApplication = new SampleApplication();
List<RecordedEvent> recordedEvents = new ArrayList<>();
try (RecordingStream rs = new RecordingStream();) {
rs.enable("jdk.FileRead");
rs.onEvent("TestEvent", e -> rs.close());
rs.onEvent("jdk.FileRead", recordedEvents::add);
rs.startAsync();
TestEvent testEvent = new TestEvent();
sampleApplication.doStuff();
testEvent.commit();
rs.awaitTermination();
}
assertFalse(recordedEvents.isEmpty());
for (RecordedEvent recordedEvent : recordedEvents) {
if(recordedEvent.getValue("path").equals("/path/to/your/file")) {
return;
}
}
fail("A readFile event wasn't found!");
}
}
初始化事件流
为了配置和捕获在自动化测试期间触发的事件,我将使用 RecordingStream
。 RecordingStream
实现 AutoCloseable
,因此我们可以在 try-with-resources 块中声明它,并允许 JVM 确保在完成时关闭和清理流。
try (RecordingStream rs = new RecordingStream();) {
...
}
启用事件
默认情况下不会启用内置 JFR 事件,例如 jdk.FileRead
,因此需要使用 rs.enable(String)
手动启用它们。自定义事件,例如 TestEvent
,默认情况下是启用的,不需要手动启用。
@Name("TestEvent")
class TestEvent extends Event {
}
...
try (RecordingStream rs = new RecordingStream();) {
...
rs.enable("jdk.FileRead");
...
}
还可以通过在初始化时将设置文件传递给 RecordingStream
来配置事件,并在其中启用相应的事件,如下面的示例所示
Configuration c = Configuration.create(Path.of("/my/config/file"));
try (RecordingStream rs = new RecordingStream(c);) {
...
}
配置事件行为
可以使用 rs.onEvent(String, Runnable)
配置触发事件时应发生的操作。在下面的代码中,TestEvent
用于关闭流,稍后会详细介绍,当 jdk.FileRead
事件发生时,它将被写入 List
以供稍后检查。
rs.onEvent("TestEvent", e -> rs.close());
rs.onEvent("jdk.FileRead", recordedEvents::add);
启动流、运行测试和关闭流
rs.startAsync()
用于在单独的线程中启动 JFR 事件流。要测试的代码(从文件进行简单读取)使用 sampleApplication.doStuff();
执行。
代码示例中最奇怪的部分是处理流的关闭。这是必要的,因为事件大约每秒刷新到 JFR 流一次,这通常意味着某些或所有事件没有写入流,导致测试结果不准确或不一致。可以使用 Thread.sleep(long)
来强制执行此操作,但在大型测试套件中,添加大量等待可能会开始对执行测试套件所需的时间产生重大影响。
TestEvent
与 rs.onEvent("TestEvent", e -> rs.close());
和 testEvent.commit();
以及 rs.awaitTermination()
提供了一种方法,可以在所有相关事件刷新到流中后立即关闭流。 testEvent.commit();
将在测试代码执行完毕后触发 rs.onEvent("TestEvent", e -> rs.close());
,而 rs.awaitTermination()
会阻止线程执行,直到线程关闭。这最大限度地减少了测试中的任何额外等待时间。或者,可以使用 JFRUnit 等库,请务必查看“其他阅读材料”部分以获取链接。
断言行为
当然,自动化测试如何处理断言被测试代码将高度依赖于上下文。此代码示例演示了如何将事件捕获到 List
中以供稍后检查。这只是一个示例,但还有许多其他有效的方法可以使用 JFR 编写自动化测试。
assertFalse(recordedEvents.isEmpty());
for (RecordedEvent recordedEvent : recordedEvents) {
if(recordedEvent.getValue("path").equals("/path/to/your/file")) {
return;
}
}
fail("A readFile event wasn't found!");
其他阅读材料
使用 Flight Recorder 事件流 API 监控事件
祝您编码愉快!