尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Junit5+Mockito实现已投票事件的测试策略

Junit5+Mockito实现已投票事件的测试策略
📅 发布时间:2026/6/30 22:21:13

一.引言

1.业务场景

在某类活动报名中,邀请投票是一个很常见的功能,用户根据某个邀请进行投票,决定是否参与或选择某个选项,然而看似简单操作的背后却隐藏着层层业务约束,比如在我的项目中此投票规则只有已报报名的用户才能投票,且同一用户不能重复投票,一旦处理不当,轻则数据错乱,重则引发用户投诉。

以我们的vote(String id,String voter)方法为例,它至少需要应对以下测试验证:

1.参数校验:id(邀请ID)和voter(投票人)可能为null,空字符串或者空白字符串,测试的时候是否能够拒绝而不是抛出空指针异常(NullPointerException)

2.存在性校验:如果传入id在数据库里不存在,服务是否返回明确的业务异常(InvitationException)

3.资格校验:投票人必须位于findEnrollers(id)返回的报名表列表中,如果该方法返回null或者空集合,我们的逻辑是否能够处理。

4.幂等性校验: 如果findVote(id,voter)已返回true(表示已投票),业务必须拦截,防止重复计票

方案:本文将利用Junit5的参数化测试(@ParameterizedTest)和Mockito的Mock隔离,结合等价类划分——边界值设计高覆盖的测试用例

二.待测试代码展示

public void vote(String id, String voter) { // 1. 参数校验 if (id == null || id.isBlank()) { throw new InvitationException(400, "邀请ID不能为空"); } if (voter == null || voter.isBlank()) { throw new InvitationException(400, "投票人不能为空"); } // 2. 邀请必须存在 Invitation invitation = repository.findInvitationById(id); if (invitation == null) { throw new InvitationException(404, "邀请不存在"); } // 3. 只有已报名的用户才能投票 Set<String> enrollers = repository.findEnrollers(id); if (enrollers == null || !enrollers.contains(voter)) { throw new InvitationException(403, "该用户未报名此邀请,无法投票"); } // 4. 不能重复投票 if (repository.findVote(id, voter)) { throw new InvitationException(403, "已投票过"); } // 5. 持久化投票记录 repository.persistVote(id, voter); }

三.测试设计

(1)确定输入域。分析被测方法投票功能参数的所有属性及其约束条件

明确参数:id(邀请ID) ,voter(投票人)

明确外部依赖(需要Mock):findEnrollers(报名列表),findVote(是否已投)。

参数校验

输入条件

有效等价类

无效等价类

Id值

非空且存在的邀请ID

Null,空字符串“”,空白字符串“ ”,不存在的Id

voter值

非空且已报名且未投票的用户

Null,空字符串“”,空白字符串“ ”

业务约束(依赖id有效的前提下)

约束条件

有效等价类

无效等价类

邀请是否存在

FindInvitationById返回非null

findInvitationById返回null

报名集合findEnrollers

返回null且包含voter的集合

返回null,返回空集合,返回的集合不包含voter

是否已投票findVote

返回false(未投票)

返回true(已投票)

(2)识别等价类 (对每个属性划分有效与无效等价类)

1.有效等价类(已报名+未投票)——>抛出InvitationException

编号

条件

有效等价类

期望结果

1

Id合法且邀请存在

Id非空且findInvitationById返回非null Invitation

——

2

voter合法且已报名

Voter非空且在findEnroller返回的集合中

——

3

未投票过

findvote返回false

成功调用persistVote(id,voter)

2.无效等价类

编号

条件

无效等价类

期望结果

1

Id为null

Id=null

抛出InvitationException(400)

2

id为空字符串

Id=“”

抛出InvitationException(400)

3

id为空白字符串

Id=“ ”

抛出InvitationException(400)

4

邀请不存在

FindInvitationById返回null

抛出InvitationException(404)

5

voter为null

Voter=null

抛出InvitationException(400)

6

voter为空字符串

Voter=“”

抛出InvitationException(400)

7

voter为空白字符串

Voter=“ ”

抛出InvitationException(400)

8

报名集合为null

FindEnroller返回null

抛出InvitationException(403)

9

报名集合为空

FindEnrollers返回空集合

抛出InvitationException(403)

10

用户为报名

FindEnrollers不包含voter

抛出InvitationException(403)

11

重复投票

FindVote返回true

抛出InvitationException(403)

(3)边界值确认

在等价类划分的基础上,我们进一步识别出需要重点测试的边界条件。边界值分析的核心思想是:程序最容易在“临界点”出错——比如空值与长度为1之间、null与空集合之间、true与false之间。因此,我们针对每个输入参数和业务依赖,选取其“刚好越过边界”的关键值进行验证。

1.参数id(邀请ID)——字符串类型

对于字符串参数,边界主要体现在“是否为 null”、“是否为空串”、“是否为空白串”以及“最短有效值”这几个临界点。这些值直接对应代码中的if (id == null || id.isBlank())校验逻辑:

编号

边界值

说明

1

Null

下边界:空值

2

“”

空字符串(长度为0)

3

“ ”

长度为1的空白字符串(刚好时空白的最小长度)

4

“a”

长度为1的非空字符串(刚好有效的最小长度)

5

正常长度的合法ID(如“inv1”)

合法值的典型代表

逻辑:null、""、" "分别代表“空引用”、“空长度”、“纯空白”三种不同的“无效”形态,确保参数校验的全面性;而"a"则验证了“只要不是空白,哪怕只有 1 个字符也能通过”的有效边界。

2.参数voter(投票人)——字符串类型

voter的边界逻辑与id完全一致,同样围绕isBlank()校验展开:

编号

边界值

说明

1

Null

下边界:空值

2

“”

空字符串(长度为0)

3

“ ”

长度为1的空白字符串

4

“u”

长度为1的非空字符串(刚好有效的最小长度)

5

正常长度的合法用户名(如“hangman”)

合法值的典型代表

3.业务约束边界

除了直接的参数校验,vote()方法还依赖于InvitationRepository的三个方法返回结果。这些依赖的返回值存在多种“边缘状态”,需要单独识别:

编号

边界值

说明

1

findInvitationByI2d返回null

邀请恰好不存在(存在性的边界)

2

FindInvitationById返回有效Invitation

邀请刚好存在(存在性的有效侧)

3

FindEnrollers返回null

报名集合为null

4

findEnrollers返回Set.of()(空集合,size=0)

报名集合刚好为空(无人报名的边界)

5

FindEnrollers返回包含voter的集合(size=1)

刚好只有该用户1人报名(包含关系的最小边界)

6

FindEnrollers返回不包含voter的集合

用户刚好不在报名集合中(不包含的边界)

7

findVote返回false(未投票)

投票状态的有效边界(刚好未投)

8

FindVote返回true(已投票)

投票状态的无效边界(刚好已投过)

逻辑:业务约束边界的选取完全映射了代码中的 3 个关键判断点 ——invitation == null、enrollers == null || !enrollers.contains(voter)、findVote(...) == true。每个判断点的“真/假”临界值都必须覆盖,才能保证分支覆盖率的完整性。

4.边界值选取总结

输入/条件

下边界(无效侧)

临界值

Id

Null->“”->“ ”

“a”(最有效)

voter

Null->“”->“ ”

“u”(最短有效串)

邀请存在性

Null(不存在)

非null Invitation(存在)

报名集合

Null->空集合(size=0)

Size=1且含voter

Voter是否报名

集合不含voter

集合含voter(size==1)

是否已投票

True(已投票,无效)

False(未投票,有效)

(4)测试代码实现

1.测试环境初始化(@BeforeEach)

我们需要先构建被测服务的框架。因为InvitationService依赖了InvitationRepository,AttachmentRepository,MemInvitationRepository等数据层组件,我们利用Mockito的mock()方法生成代理对象,并通过构造函数注入到Service中。

2.无效等价类

在voteInvalidData()数据源中,定义了11钟无效场景

然后再想怎样才能让这11个用例在不同的异常下触发,运用了switch(description)动态路由

voteInvalidData()中的第一个参数(如"邀请ID为null")不仅仅是一个展示名称。在testVoteInvalid方法中,我们利用switch(description)将其转化为 Mock 行为的动态路由器。

当 JUnit 5 遍历数据源时,每一条Arguments都会触发一次testVoteInvalid的执行。进入方法体后,switch根据当前的description值,精准地为mockRepository配置对应的when...thenReturn行为——例如遇到"重复投票",就让findVote返回true;遇到"findEnrollers返回null",就让findEnrollers返回null。

这种设计的最大好处是:将“变化的部分”(不同场景下的 Mock 设置)与“不变的部分”(统一的异常断言)彻底解耦。未来如果产品经理新增一个“活动已结束不能投票”的约束,我们只需在数据源中追加一行Arguments.of("活动已结束", "inv-end", "userX"),并在switch中新增一个case即可——完全不需要修改已有的测试逻辑,符合开闭原则(OCP)。

3.有效等价类

在voteValidData()数据源中只列了一条数据,但它验证了完整的 Happy Path,确保业务逻辑顺利走完并调用了最终的保存方法。

核心是验证repository的persistVote被调用了一次,且参数正确

(5)测试运行效果

相关新闻

  • 2026年深度测评:10款好用的降AI率网站,部分无限免费降AI!必备收藏
  • 数据结构基础——第三板块:树与二叉树(Trees Binary Trees)
  • 影视摄影行业数据恢复经典案例全解_东方护航数据恢复深圳店

最新新闻

  • VMware虚拟机磁盘压缩实操手册:从120GB到45GB,零风险释放空间的7个关键命令
  • 一文看懂 MCP:为什么 OpenAI、Claude、Cursor 都在支持这个 AI 世界的 USB-C
  • Agentic AI 复利效应:从自动化到经验积累的智能体系统设计
  • 最好用的AI论文平台推荐(从文献整理到论文成稿全流程)适合全体毕业生
  • 6款论文降AIGC工具实测:AI率秒归安全区,学生党狂喜款
  • VMware虚拟机安装Windows 3.1并配置声卡驱动完整指南

日新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号