Java串口单元测试:如何虚拟串口替代硬件传感器?
当然可行!而且有几种靠谱的方案能帮你在没有硬件传感器的情况下,顺利测试串口通信逻辑。下面我给你拆解下具体思路和实现方式:
方案一:依赖注入+自定义串口适配器(最推荐)
因为jssc的SerialPort是具体类而非接口,直接模拟会有局限。我们可以通过适配器模式给串口操作套一层接口,让业务逻辑和具体的串口实现解耦,测试时替换成模拟实现即可。
步骤1:定义串口操作接口
先抽象出你需要的串口方法,比如:
public interface SerialPortHandler { boolean open() throws SerialPortException; void close() throws SerialPortException; byte[] read(int byteCount) throws SerialPortException; void write(byte[] data) throws SerialPortException; // 按需添加其他方法,比如设置波特率、注册数据监听等 }
步骤2:生产环境的JSSC实现
基于jssc写实际运行时的适配器:
public class JsscSerialPortHandler implements SerialPortHandler { private final SerialPort serialPort; public JsscSerialPortHandler(String portName) { this.serialPort = new SerialPort(portName); } @Override public boolean open() throws SerialPortException { return serialPort.openPort(); } @Override public void close() throws SerialPortException { serialPort.closePort(); } @Override public byte[] read(int byteCount) throws SerialPortException { return serialPort.readBytes(byteCount); } @Override public void write(byte[] data) throws SerialPortException { serialPort.writeBytes(data); } }
步骤3:测试用的模拟适配器
写一个完全可控的模拟类,用来模拟传感器的行为:
import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.Arrays; public class MockSerialPortHandler implements SerialPortHandler { private boolean isOpen = false; private final Queue<byte[]> incomingData = new LinkedList<>(); private final List<byte[]> outgoingData = new ArrayList<>(); @Override public boolean open() { isOpen = true; return true; } @Override public void close() { isOpen = false; } @Override public byte[] read(int byteCount) { return incomingData.poll(); } @Override public void write(byte[] data) { outgoingData.add(Arrays.copyOf(data, data.length)); } // 测试辅助方法:模拟传感器发送数据到串口 public void simulateSensorInput(byte[] data) { incomingData.add(data); } // 测试辅助方法:获取业务代码发送出去的数据,验证逻辑正确性 public List<byte[]> getOutgoingData() { return outgoingData; } public boolean isOpen() { return isOpen; } }
单元测试示例
在测试中把模拟适配器注入到你的业务逻辑类里,就能完全控制串口交互:
import org.junit.Test; import static org.junit.Assert.*; public class SensorCommunicatorTest { @Test public void testSensorDataReading() throws SerialPortException { // 初始化模拟串口 MockSerialPortHandler mockPort = new MockSerialPortHandler(); // 注入到你的业务类中 SensorCommunicator communicator = new SensorCommunicator(mockPort); // 模拟传感器发送数据 byte[] testData = new byte[]{0x01, 0x02, 0x03}; mockPort.simulateSensorInput(testData); // 调用业务方法读取数据 byte[] receivedData = communicator.readSensorData(); // 验证读取结果正确 assertArrayEquals(testData, receivedData); // 验证业务代码是否发送了预期指令(比如请求数据的指令) assertEquals(1, mockPort.getOutgoingData().size()); } }
方案二:用Mockito直接模拟JSSC的SerialPort类
如果不想写适配器,也可以用Mockito这类Mock框架直接模拟SerialPort类。不过要注意:jssc部分方法是final的,Mockito默认不支持模拟final方法,需要开启Mockito Inline支持(在依赖中添加mockito-inline包)。
示例代码:
import org.junit.Test; import static org.mockito.Mockito.*; import static org.junit.Assert.*; public class SensorCommunicatorMockitoTest { @Test public void testWithMockedSerialPort() throws SerialPortException { // 模拟SerialPort实例 SerialPort mockSerialPort = mock(SerialPort.class); // 预设方法返回值:模拟打开串口成功 when(mockSerialPort.openPort()).thenReturn(true); // 预设读取数据的返回值 when(mockSerialPort.readBytes(3)).thenReturn(new byte[]{0x01, 0x02, 0x03}); // 注入到业务类 SensorCommunicator communicator = new SensorCommunicator(mockSerialPort); communicator.connect(); byte[] data = communicator.readSensorData(); // 验证结果 assertArrayEquals(new byte[]{0x01, 0x02, 0x03}, data); // 验证openPort方法被调用过一次 verify(mockSerialPort, times(1)).openPort(); } }
方案三:虚拟串口库(适合集成测试)
如果需要更接近真实环境的双向串口交互(比如模拟两个串口互相通信),可以用Java虚拟串口库,比如jSerialComm(它支持创建虚拟串口对)。不过这个方案更适合集成测试,单元测试用前面两种Mock方式更轻量高效。
示例代码(基于jSerialComm):
import com.fazecast.jSerialComm.SerialPort; public class VirtualSerialTest { public static void main(String[] args) { // 创建一对虚拟串口,比如COM3和COM4 SerialPort[] virtualPorts = SerialPort.createVirtualPortPair("COM3", "COM4"); SerialPort sensorSidePort = virtualPorts[0]; SerialPort testSidePort = virtualPorts[1]; // 业务代码连接COM3,测试代码通过COM4发送/接收数据,模拟传感器行为 sensorSidePort.openPort(); testSidePort.openPort(); // 测试代码发送模拟数据 testSidePort.writeBytes(new byte[]{0x01, 0x02, 0x03}, 0, 3); // 业务代码读取数据... } }
内容的提问来源于stack exchange,提问作者Gadi
相关产品推荐
相关产品推荐

