Spring Boot 单元测试怎么写?物联网平台 MockMvc 接口测试与 Mockito 消息链路测试实战(附 JaCoCo 覆盖率报告)
真实运行截图一、功能都写完了为什么还要写测试我这个物联网设备接入平台Spring Boot 3.5 EMQX MySQL从设备注册、MQTT 接入、消息入库到密钥轮换功能主线已经跑通了。但每次面试话题走到质量保障我都心虚——功能是我手点出来的谁保证我下次改一行代码不会把注册接口改挂测试在项目里的位置是这样的接入层、数据层、安全层的功能代码是存货测试是保险。每次给设备加新字段、给消息处理加新分支跑一遍测试就知道有没有把老功能弄坏。这在物联网领域尤其重要——设备的认证/ACL 逻辑改坏了不是页面白屏而是陌生设备能连进你的 Broker。先交代结论项目现在有11 个测试类、37 个测试用例mvn test全绿见第一张截图整体行覆盖率 76%。下面讲这 37 个用例怎么组织的。二、两类测试的分工谁该连数据库谁不该我的测试分两类对应测试金字塔的两层类型代表依赖测什么纯单元测试TelemetryMessageHandlerTest不起 SpringRepository 全部 Mock消息处理的业务逻辑分支Web 层测试DeviceControllerTest、AdminTokenInterceptorTest等SpringBootTest H2 内存库HTTP 接口从过滤器到数据库的整条链路分工原则一句话逻辑分支多的用纯单元测试跑得快跨层协作的用 MockMvc 测真实行为。消息处理类里全是 if/else 分支未知设备、productKey 不匹配、非法 JSON……起一次 Spring 上下文要好几秒纯单元测试毫秒级跑完而注册接口到底返回 201 还是 409这种问题Mock 不出来必须让请求真穿过拦截器和 JPA。三、消息链路纯单元测试Mockito ArgumentCaptor被测的TelemetryMessageHandler是 MQTT 消息的入口来源src\main\java\com\iothub\mqtt\TelemetryMessageHandler.java核心逻辑解析主题up/{productKey}/{deviceId}校验设备存在且 productKey 匹配再按up/st分流。TestvoidtelemetryMarksDeviceOnlineAndSavesDataPoint(){when(deviceRepository.findById(1L)).thenReturn(Optional.of(device));handler.handle(up/th-sensor/1,{\temp\:25.5,\humi\:60});assertThat(device.getStatus()).isEqualTo(online);verify(deviceRepository).save(device);// ArgumentCaptor 抓住传给 Mock 的对象逐字段断言ArgumentCaptorDataPointcaptorArgumentCaptor.forClass(DataPoint.class);verify(dataPointRepository).save(captor.capture());assertThat(captor.getValue().getDeviceId()).isEqualTo(1L);assertThat(captor.getValue().getProductKey()).isEqualTo(th-sensor);assertThat(captor.getValue().getPayload()).contains(25.5);}来源src\test\java\com\iothub\mqtt\TelemetryMessageHandlerTest.java类上用ExtendWith(MockitoExtension.class)不起 Spring 容器这个类一共 5 个用例专门冲着异常分支去的未知设备直接丢弃、productKey 不匹配丢弃、非法 JSON 丢弃、状态消息只改设备状态不存数据点。为什么这么设计因为消息链路的正常路径只有一条出事的永远在异常路径——伪造的 productKey、断电设备发来的脏数据都得在这里拦住。测试里把每个丢弃分支都钉死以后谁改这段代码漏了校验测试立刻红。四、接口层测试MockMvc 从拦截器一路测到数据库注册接口是平台最核心的入口DeviceControllerTest三个用例把三种结局都锁死了TestvoidregisterSuccessReturnsSecretOnce()throwsException{// ... post /api/devices 断言 201 code0 secret/clientId 非空 ...// 从响应里抠出明文密钥再验证库里存的确实是哈希而不是明文DevicesaveddeviceRepository.findAll().stream().filter(d-test-温湿度-001.equals(d.getDeviceName())).findFirst().orElseThrow();assertThat(saved.getSecretHash()).isNotEqualTo(secret).isEqualTo(DeviceService.sha256(secret));}来源src\test\java\com\iothub\device\DeviceControllerTest.java这个用例我最得意的地方不是测了 201而是最后三行从响应里拿到明文密钥再断言数据库里存的不是明文、且等于 SHA-256(明文)。接口测试的价值就在这——它能穿透到数据库验证副作用这是 Postman 手点给不了的。另一个套路是写操作测权限AdminTokenInterceptorTest用 5 个用例验证管理令牌无令牌 401、错令牌 401、对令牌放行、GET 公开、EMQX 回调豁免。注意类上有MockBean private MqttConnection mqttConnection——起 Spring 上下文但不让它真连 Broker这是物联网项目 Web 测试的关键技巧测接口不测网络。五、测试抓出来的两个真 Bug测试不是摆设这两个真 Bug 就是它抓的Bug 1浏览器要个 favicon.ico日志里炸出一条 500。ErrorResponseTest里前两个用例是专门的回归测试——之前没单独处理NoResourceFoundException任何浏览器访问/favicon.ico都会被全局异常兜底捕获返回 500还刷一条 ERROR 堆栈。修完之后测试钉死现在必须返回 404 code404。Bug 2MockMvc 默认按 ISO-8859-1 解码响应中文断言全乱码。测静态看板页时getContentAsString()拿到的中文全是问号断言设备实时监控看板永远失败。解法是显式传编码getContentAsString(StandardCharsets.UTF_8)ErrorResponseTest源码里有注释标注。这种坑不写测试你根本遇不到因为浏览器会自己猜编码。六、跑测试 JaCoCo拿真实数字别拍脑袋跑测试和覆盖率不需要改 pom.xml命令行直接拼插件目标mvn org.jacoco:jacoco-maven-plugin:0.8.12:prepare-agenttestorg.jacoco:jacoco-maven-plugin:0.8.12:report今天的真实结果见前两张截图Tests run: 37, Failures: 0, Errors: 0, Skipped: 0BUILD SUCCESS。覆盖率报告在target\site\jacoco\index.html真实数字包行覆盖说明com.iothub.device96%注册/生命周期MockMvc 覆盖最全com.iothub.common94%拦截器/异常处理回归测试钉死com.iothub.data89%查询接口与保留任务com.iothub.mqtt75%认证/ACL 很高MqttConnection 重连逻辑低com.iothub.ws13%WebSocket 推送循环基本没测整体76%指令覆盖 79%、分支覆盖 71%说句实话ws 包那 13% 我不打算现在补。WebSocket 推送是定时循环会话广播Mock 它的成本远超收益我的取舍是用真实浏览器开两个标签页人工验收过M4 里程碑那次。覆盖率是体检报告不是 KPI——它最大的用处是告诉你风险在哪而不是逼你凑 100%。面试里能讲清哪块没测、为什么暂不补比一句覆盖率 90%更加分。面试考点小结测试金字塔分工逻辑分支 → Mockito 纯单元测试跨层协作 →SpringBootTest MockMvc H2 内存库MockBean的用途隔离外部依赖MQTT Broker测业务不测网络ArgumentCaptor断言传给依赖的对象长什么样而不只是调用过没有回归测试Bug 修完必须留一个用例钉死防止复发覆盖率工具JaCoCo 不改 pom 也能用 CLI 挂上覆盖率看分布不看总分。写在最后我是软件工程在读正在从零搭一个物联网设备接入平台Spring Boot EMQX MySQL这篇是质量保障这条支线的第一篇。写这篇之前我以为测试是任务写完发现那两个真 Bug 已经回本了。下一篇计划写 EMQX 共享订阅或 Kafka 解耦。你项目里的测试覆盖率是多少评论区聊一句比点赞更让我高兴。本文所有数据均为本机实测2026-10-10mvn test37 个用例全绿、JaCoCo 覆盖率来自target\site\jacoco\真实报告代码均摘自项目D:\dev\iot-device-platform真实源码并标注来源文件截图为真实测试输出与报告画面。

相关新闻

宁波执业律师张娟:配偶背的债你要还吗——夫妻共同债务认定规则拆解

宁波执业律师张娟:配偶背的债你要还吗——夫妻共同债务认定规则拆解

结论先行:配偶单方所负的债务,并不天然由你连带承担。民法典第1064条确立了共债共签原则——共同签字或事后追认、为家庭日常生活需要所负的债务才是共同债务;超出日常生活需要的,原则上由举债方自担,除非债权人能证明…

2026/10/14 3:23:34 阅读更多 →
铝制小青瓦筑就江南院墅 复刻黛瓦白墙的东方栖居

铝制小青瓦筑就江南院墅 复刻黛瓦白墙的东方栖居

坐落于苏州的拙政江南,以拙政园为文脉基底,打造连片式低密新中式院墅群落。白墙围合私家庭院,黛瓦错落铺陈屋面,亭廊婉转、叠石理水,将苏州园林的雅致意境藏进每一方栖居空间。项目全线屋面采用铝制小青瓦,以现代建材工艺复刻传统江南小青瓦的形与韵,更从根源破解传统陶瓦的运维…

2026/10/14 3:23:34 阅读更多 →
中国起名服务正在发生什么变化?从“大师经验”到专业化、品牌化服务

中国起名服务正在发生什么变化?从“大师经验”到专业化、品牌化服务

很长时间里,人们对于“找人起名”的想象都非常简单: 找一位有经验的老师, 报出生时间, 老师根据经验给出几个名字。 整个服务高度依赖: “这个大师厉不厉害”。 但今天再观察国内专业起名市场,会发现它已经…

2026/10/14 3:22:34 阅读更多 →

最新新闻

办公设备管理系统OAMS源码实战:从台账混乱到扫码即用

办公设备管理系统OAMS源码实战:从台账混乱到扫码即用

简介:办公设备管理系统OAMS是一套面向企事业单位设备管理员与信息化开发者的全生命周期管理解决方案,覆盖采购、入库、领用、维修、报废等核心环节,适合需要落地资产台账、审批流程与统计报表的Java Web项目学习与二次开发。资源包共451个文件…

2026/10/14 4:00:41 阅读更多 →
SpringBoot2+Vue3流浪宠物管理系统:前后端分离架构实战解析

SpringBoot2+Vue3流浪宠物管理系统:前后端分离架构实战解析

1. 这个系统解决的是什么问题:流浪宠物救助的数字化盲区说实话,市面上打着"宠物管理系统"旗号的项目源码并不少,但真正能落地到流浪动物救助场景的却不多。大部分所谓管理系统要么是单纯的前端页面堆砌,要么后端只做了简…

2026/10/14 4:00:41 阅读更多 →
Matlab实现CNN-GRU多特征分类预测:从数据准备到模型落地

Matlab实现CNN-GRU多特征分类预测:从数据准备到模型落地

涉及到用Matlab做时序或者多维特征数据的分类预测时,CNN-GRU这个组合近年来出镜率非常高。一方面卷积结构擅长局部特征提取,另一方面GRU门控循环单元对序列依赖关系的建模能力又比普通前馈网络强很多,两个搭在一起后,既能处理多维…

2026/10/14 4:00:41 阅读更多 →
Claude Code + git worktree 多分支并行开发实战指南

Claude Code + git worktree 多分支并行开发实战指南

最近半年我一直在重度使用 Claude Code 做日常开发,手头同时维护好几个功能分支是常有的事。一开始我还是老一套:开一个终端窗口,在一个工作目录里反复git checkout切换分支,切来切去不仅脑子乱,更麻烦的是 Claude Cod…

2026/10/14 4:00:41 阅读更多 →
中小团队 CI/CD 工具选哪个:2026 年主流平台对比与 Gitee 方案解析

中小团队 CI/CD 工具选哪个:2026 年主流平台对比与 Gitee 方案解析

中小团队选择 CI/CD 工具的核心结论是:没有"最好"的工具,只有匹配团队规模、技术栈与合规要求的方案;据 JetBrains 2025 年开发者生态系统报告,GitHub Actions 以 33% 采用率领跑,Jenkins 以 28% 紧随其后&a…

2026/10/14 4:00:41 阅读更多 →
从零构建每日晨报:信息过滤系统的设计与实现

从零构建每日晨报:信息过滤系统的设计与实现

1. 一份“每日晨报”到底在解决什么问题每天早上七点半,我习惯性打开自己维护的一个小工具,屏幕上会弹出一份排版干净的简报:昨天圈子里发生了什么、有哪些值得点进去看的链接、今天有什么值得留意的节点。这份东西我管它叫“每日晨报”&…

2026/10/14 3:59:41 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →