嵌入式Java单元测试框架搭建:在硬件资源受限场景下完成代码自动化测试
受限嵌入式Java做单测,核心不是把JUnit全套搬进板子,而是“业务算法离板跑、硬件访问靠抽象、板端只跑冒烟”。GPIO、ADC、串口、Modbus驱动不直连测试类,统一成HardwarePort接口,主机用Fake/Mock造数据,ARM小内存设备只跑精简用例。
一、硬件抽象与桩件设计
先定义端口接口,把所有寄存器、文件节点、JNI调用关在里面:
public interface HardwarePort {
int readRaw(int channel);
void writeRaw(int channel, int value);
long lastUpdated(int channel);
}
测试用Fake实现,可人为制造超量程、超时、固定值:
public class FakeHardwarePort implements HardwarePort {
private final int[] values;
private long ts = System.currentTimeMillis();
public FakeHardwarePort(int[] values) { this.values = values; }
public int readRaw(int ch) { return values[ch]; }
public void writeRaw(int ch, int v) { values[ch] = v; }
public long lastUpdated(int ch) { return ts; }
}
真实设备用JNI/字符设备实现同一接口;单测不加载so、不碰/dev,避免板端资源争用和状态不可复现。
二、业务单测与断言
以ADC量程清洗为例,零漂、满量程、死区都在主机JUnit覆盖:
import org.junit.Test;
import static org.junit.Assert.*;
public class AdcCleanTest {
@Test
public void linearScaleOk() {
HardwarePort hw = new FakeHardwarePort(new int[]{32768, 0, 65535});
AdcService svc = new AdcService(hw, 0, 4.0, 20.0); // ch0,0~65535->4~20mA
double v = svc.readEngineering(0);
assertEquals(12.0, v, 0.01);
}
@Test
public void outOfRangeMarkBad() {
HardwarePort hw = new FakeHardwarePort(new int[]{70000});
AdcService svc = new AdcService(hw, 0, 4.0, 20.0);
assertTrue(Double.isNaN(svc.readEngineering(0)));
}
}
AdcService只依赖HardwarePort,不依赖具体板子;协议解析、点表映射、班次聚合、离线补发都用同样方式做纯逻辑单测。Mockito/jMock仅用于复杂依赖,小内存项目尽量用Fake代替动态字节码Mock,减少启动开销。
三、受限板端冒烟测试
256MB~1GB网关可跑裁剪测试包,不跑全量回归。用JUnit ConsoleLauncher或自建main执行指定Suite:
public class BoardSmokeTest {
public static void main(String[] args) {
int fail = 0;
HardwarePort hw = new JniHardwarePort();
AdcService svc = new AdcService(hw, 0, 4.0, 20.0);
double v = svc.readEngineering(0);
if (v < 3.9 || v > 20.1) { System.out.println("ADC SMOKE FAIL"); fail++; }
System.out.println(fail == 0 ? "SMOKE OK" : "SMOKE ERR:" + fail);
System.exit(fail);
}
}
JVM限小堆,避免测试期OOM:
java -Xms32m -Xmx96m -XX:+UseSerialGC \
-cp test.jar:app.jar BoardSmokeTest
只验关键链路:硬件读值、协议转换、本地缓存、MQTT组包;重UI、重历史库放到主机CI。
四、离板CI与覆盖率
GitHub/GitLab Runner跑主机全量:src/test/java覆盖协议、清洗、告警、报表;硬件用Fake,串口/网络用回环或仿真服务。 Maven示例:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<executions>
<execution><goals>
<goal>prepare-agent</goal>
</goals></execution>
<execution><id>report</id><phase>test</phase><goals>
<goal>report</goal>
</goals></execution>
</executions>
</plugin>
JaCoCo动态插桩收集行/分支覆盖,受限设备用offline模式更省内存。 门禁建议核心清洗、协议解析分支覆盖≥90%,UI和驱动适配只做连通冒烟。
五、落地要点
所有时间、随机、文件、网络都走可注入接口,测试不依赖墙钟和真实网卡;
寄存器状态、告警阈值、Modbus异常码用Fake预置,保证用例可重复;
小内存板不用PowerMock/JMockit重装字节码,能用构造注入就用构造注入;
CI出HTML覆盖报告,板端OTA前跑冒烟jar,返回SMOKE OK再切生产;
硬件仿真缺件时,用JSON定义虚拟设备、容器跑回放,补PLC/传感器异常场景。
按“接口抽象+Fake桩+主机JUnit全量+板端小堆冒烟+JaCoCo轻量覆盖”搭建,既能在笔记本/CI秒级回归,又能在嵌入式网关低内存环境自动验关键链路,避免把重型测试框架硬塞进终端。





