工具

测试(或在没有 PLC 的情况下使用 PLC4X)

qianmoQqianmoQ· 更新于 2026-10-01· 阅读 9 分钟· 0 次阅读

登录后可跨设备保存划线和私人笔记登录

模拟驱动

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.PlcEntityManagerTest
  • org.apache.plc4x.java.opm.PlcEntityManagerComplexTest

以及更多的测试类,尤其是 OPM 模块中的测试类。

评论

登录后参与评论

正在加载评论…