测试(或在没有 PLC 的情况下使用 PLC4X)
模拟驱动
PLC4X 提供了一个 Mock Driver(模拟驱动),它最初是为单元测试而实现的,至今仍是其主要用途。不过,当没有硬件 PLC 可用时,这个驱动也非常适合用来随意体验一下 PLC4X API。该驱动位于 Maven 模块中。
<dependency>
<groupId>org.apache.plc4x</groupId>
<artifactId>plc4j-driver-mock</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>Mock 驱动的连接字符串语法为 mock:{name-of-the-connection}。因此你可以同时使用多个 Mock 设备。
Mock 驱动除了将所有请求转发给一个「虚拟设备」之外不做任何其他事情,这个虚拟设备由我们提供,用于控制所有响应并对其进行监控,例如用于单元测试。Mock 设备的接口是
public interface MockDevice {
Pair<PlcResponseCode, PlcValue> read(String fieldQuery);
PlcResponseCode write(String fieldQuery, Object value);
Pair<PlcResponseCode, PlcSubscriptionHandle> subscribe(String fieldQuery);
void unsubscribe();
// ...
}简单示例
假设我们有一段无法控制的代码,或者想要测试它的功能。这时可以使用 Mock 驱动来实现,方式如下。
首先,建立一个新的 Mock 连接(与其他任何连接的建立方式相同):
PlcMockConnection connection = (PlcMockConnection) PlcDriverManager.getDefault().getConnectionFactory().getConnection("mock:my-mock-connection");你看,我们把连接直接转换成了一个 PlcMockConnection。这样做是因为我们需要将一个设备连接到这个 Mock 连接上。
具体实现如下所示的代码片段:
connection.setDevice(mockDevice);在这里,我们将一个 MockDevice 的实例传递给它,该实例可以是该接口的一个简单实现,例如
MockDevice mockDevice = new MockDevice() {
Pair<PlcResponseCode, PlcValue> read(String fieldQuery) {
System.out.println("I got a read to " + fieldQuery);
return Pair.of(PlcResponseCode.OK, new PlcString("hello"));
}
PlcResponseCode write(String fieldQuery, Object value) {
System.out.println("I got a write to " + fieldQuery + " with the value " + value);
return PlcResponseCode.OK;
}
// ...
}这将针对每个请求仅返回一个字符串值 hello,并把所有读写请求打印到控制台。
使用 Mock 驱动进行单元测试
要在单元测试中使用 Mock 驱动,最简单的方式是将其 MockDriver 实例生成为 Mockito(或任何其他框架)的 Mock 对象,如下例所示
MockDevice mockDevice = Mockito.mock(MockDevice.class);
PlcMockConnection connection = (PlcMockConnection) PlcDriverManager.getDefault().getConnectionFactory().getConnection("mock:my-mock-connection");
connection.setDevice(mockDevice);
// Populate the Mock to avoid a NPE
when(mockDevice).read(anyString()).thenReturn(Pair.of(PlcResponseCode.OK, new PlcString("hello")));
// Some Demo code that uses the same Driver Manager and either the connection from above
// or at least mock:my-mock-connection as connection string
// Here: send a request to the field "MyAdress"
connection
.readRequestBuilder
.addItem("station", "MyAdress")
.build()
.execute()
.get();
// Check that the we really issued a Read request to the Field "MyAdress"
verify(mockDevice).read(eq("MyAdress"));但是由于 MockDriver 使用了静态的 Mock 连接注册表,因此以下代码同样有效。
MockDevice mockDevice = Mockito.mock(MockDevice.class);
// Setup
PlcMockConnection connection = (PlcMockConnection) PlcDriverManager.getDefault().getConnectionFactory().getConnection("mock:my-mock-connection");
connection.setDevice(mockDevice);
// Populate the Mock to avoid a NPE
when(mockDevice).read(anyString()).thenReturn(Pair.of(PlcResponseCode.OK, new PlcString("hello")));
// Some Demo code that uses the same Driver Manager and either the connection from above
// or at least mock:my-mock-connection as connection string
// Here: send a request to the field "MyAdress"
// and we build up a new Connection
try (PlcConnection conn = PlcDriverManager.getDefault().getConnectionFactory().getConnection("mock:my-mock-connection")) {
conn
.readRequestBuilder
.addItem("station", "MyAdress")
.build()
.execute()
.get();
} catch (Exception e) {
// do nothing
}
// Check that the we really issued a Read request to the Field "MyAdress"
verify(mockDevice).read(eq("MyAdress"));上面的代码片段表明,被测部分除了连接字符串之外,确实无需与测试代码共享任何东西。
结论
上述示例表明,MockDriver 驱动不仅可以用来试用 API,更是对使用 PLC4X API 的代码进行单元测试的强大工具。所需做的只是传入一个模拟连接的实例,或者直接使用与准备模拟设备时相同的连接字符串(例如来自测试配置)。更多(更复杂的)用例示例可以在 PLC4X 代码库中找到,例如以下类中。
org.apache.plc4x.java.opm.PlcEntityManagerTestorg.apache.plc4x.java.opm.PlcEntityManagerComplexTest
以及更多的测试类,尤其是 OPM 模块中的测试类。
评论
登录后参与评论
KnowForge