【SpringBoot】单元测试实战演示及心得分享

devtools/2025/2/11 18:54:21/

目录

1.指定测试标准

2.设计测试用例

3.测试集示例

4.跑测试集


1.指定测试标准

单元测试会用到mock和junit的内容,作者前文有详解,可移步:

Spring Boot单元测试-CSDN博客

mockito的详细使用-CSDN博客

1.1.测哪一层?

以当前后端标准的MVC分层来说,后端代码分为controller、service、dao三层。首先我们要先确定这三层里面去测试哪一层?单元测试的核心目的是什么:

覆盖业务代码

按标准的来说的话controller层是系统对外暴露的API,这一层级只负责做一些请求和参数的处理;service层用来编写具体的业务逻辑;dao层负责与数据库进行交互。所以我们应该测service层。

1.2.如何判断测试是否通过?

测试的输出结果和我们期望的输出结果是一致的,测试就通过了。怎么判断喃?

用Assert断言

Assert不要到处去用,在测试用例的最后用它来判断一下输出结果是不是期望值即可。

1.3.mock掉哪些内容?

mock我们主要拿来干两件事儿:

  • mock掉对数据库的操作,避免引起数据的改动,也就是说要mock掉dao层的方法

  • mock掉没办法达到的地方,比如有些地方不影响代码逻辑,但是在测试的时候不好造出来,这些不可达的地方可以mock掉。

mock我们要mock两种情况:

  • mock返回值

  • mock行为

mock返回值,比如:

Train train = new Train();
String id = UUID.randomUUID() + "";
train.setKeyId(id);
when(trainDao.getDetail(any(Train.class))).thenReturn(train);

mock行为有些时候是主动的,我们想去定义实体的具体行为,有时候是被动的,比如要mock的dao方法没有返回值该,我们就只能通过去mock行为来使得它不去操作数据库,反正核心就是不让它去操作数据库。

比如以下方法:

void trainDetailDao.updateList(XXX)

用doAnswer去mock它的响应:

@Test
public void modifyTrainDetails(){?  TrainDetailList trainDetails = new TrainDetailList();?  TrainDetail trainDetail = new TrainDetail();?  trainDetail.setKeyId(UUID.randomUUID()+"");?  trainDetails.add(trainDetail);?  doAnswer(invocation -> {? ? ?  List<TrainDetail> trainDetailList = (List<TrainDetail>) invocation.getArguments()[0];? ? ?  Assert.assertEquals(trainDetails.getItems(), trainDetailList);? ? ?  return trainDetails;?  }).when(trainDetailDao).updateList(any());?  trainDetailBaseSvr.modifyTrainDetails("",trainDetails);
}

2.设计测试用例

一个接口只需要一个测试用例吗?有时候是不够的。

衡量对一个接口的单元测试是不是到位了,核心指标是看它的分支覆盖率。代码种的一个方法里面有些时候会存在一些选择分支(带判断性质的语句),我们设计测试用例的时候要考虑覆盖掉所有分支。

最好的办法就是画个流程图,设计测试用例的时候要覆盖掉所有流程分支,以下以用户买猪肉为一个例子:

灰色的节点就是要mock掉的

图片

细化成流程图,流程图的所有出口就是要覆盖的分支,有几个出口,就应该有几个用例,有几个测试方法:

3.测试集示例

以下是作者在工作中编写的一个测试集用例,演示了一个简单的对增删改查方法的覆盖。里面演示了如何覆盖有返回值的方法和没有返回值的方法。

这里有几个技巧分享一下:

首先是要mock掉dao层的话,我们就要把service里面依赖的dao换成mock出来的dao,这里需要用反射的方式强行访问到service里面的dao,然后把它替换掉。其次mock掉dao层之后直接new service就行,完全不需要用到自动注入,也就是不需要用到IOC,也就不需要用到@RunWith(XXX.class) @SpringBootTest(classes = XXX.class)之类的注解来启动SpringBoot了。这样跑测试用例的时候,省去了启动时间,会快很多。

public class ExaminationBaseSvrTest extends PropertyControllerBase {IExaminationBaseSvr examinationBaseSvr;ExaminationTargetService examinationTargetService;private ExaminationDao examinationDao;private IDataDicItemBaseMgeSvr dataDicItemBaseMgeSvr;private DataDictionaryItemDao dataDictionaryItemDao;@Beforepublic void setUp() throws Exception{examinationBaseSvr = new ExaminationBaseSvr();Field field = ExaminationBaseSvr.class.getDeclaredField("examinationDao");Field dataDicItemBaseMgeSvrField = ExaminationBaseSvr.class.getDeclaredField("dataDicItemBaseMgeSvr");Field dataDictionaryItemDaoField = DataDicItemBaseMgeSvr.class.getDeclaredField("dataDictionaryItemDao");field.setAccessible(true);dataDicItemBaseMgeSvrField.setAccessible(true);dataDictionaryItemDaoField.setAccessible(true);examinationDao = mock(ExaminationDao.class);dataDictionaryItemDao=mock(DataDictionaryItemDao.class);dataDicItemBaseMgeSvr=mock(DataDicItemBaseMgeSvr.class);field.set(examinationBaseSvr, examinationDao);dataDicItemBaseMgeSvrField.set(examinationBaseSvr,dataDicItemBaseMgeSvr);dataDictionaryItemDaoField.set(dataDicItemBaseMgeSvr,dataDictionaryItemDao);}@Testpublic void addExamination(){when(examinationDao.insert(any())).thenReturn(1);Examination examination = new Examination();examination.setKeyId(UUID.randomUUID()+"");Assert.assertEquals(examinationBaseSvr.addExamination("",examination),examination);}@Testpublic void addExcaminations(){ExaminationList examinations = new ExaminationList();Examination examination = new Examination();examination.setKeyId(UUID.randomUUID()+"");examinations.add(examination);doAnswer(invocation -> {List<Examination> examinationList = (List<Examination>)invocation.getArguments()[0];Assert.assertEquals(examinationList,examinations.getItems());return 1;}).when(examinationDao).insertList(any());examinationBaseSvr.addExaminations("",examinations);}@Testpublic void modifyExamination(){when(examinationDao.update(any())).thenReturn(1);Examination examination = new Examination();examination.setKeyId(UUID.randomUUID()+"");Assert.assertEquals(examinationBaseSvr.modifyExamination("",examination),examination);}@Testpublic void modifyExaminations(){ExaminationList examinations = new ExaminationList();Examination examination = new Examination();examination.setKeyId(UUID.randomUUID()+"");examinations.add(examination);doAnswer(invocation -> {List<Examination> examinationList = (List<Examination>)invocation.getArguments()[0];Assert.assertEquals(examinationList,examinations.getItems());return 1;}).when(examinationDao).updateList(examinations.getItems());examinationBaseSvr.modifyExaminations("",examinations);}@Testpublic void deleteExamination(){Examination examination = new Examination();examination.setKeyId(UUID.randomUUID()+"");when(examinationDao.update(any())).thenReturn(1);when(examinationDao.getDetail(any())).thenReturn(examination);Assert.assertEquals(examinationBaseSvr.deleteExamination("",examination.getKeyId(),false),1);}@Testpublic void deleteExaminations(){ExaminationList examinations = new ExaminationList();Examination examination = new Examination();examination.setKeyId(UUID.randomUUID()+"");examinations.add(examination);doAnswer(invocation -> {List<Examination> examinationList = (List<Examination>)invocation.getArguments()[0];Assert.assertEquals(examinationList,examinations.getItems());return 1;}).when(examinationDao).updateList(any());examinationBaseSvr.deleteExamination("",examinations,false);}
}

4.跑测试集

测试类写完之后,类名旁边有一个run的图标,点击即可跑整个测试集。其中有普通的run以及带覆盖率报告的run:

选择带覆盖率的run之后会显示覆盖率:

相看类里面具体是哪些代码段被覆盖了,可以在跑完测试集后进入具体的被测试类,代码行旁边会有颜色条,绿色表示被cover的内容:


http://www.ppmy.cn/devtools/158001.html

相关文章

APP广告变现如何优化广告填充率,提升变现收益?

APP广告变现对接聚合广告平台可以提升广告变现效率&#xff0c;最大化广告收益。#APP广告变现# 一般来说&#xff0c;广告填充率越高&#xff0c;意味着广告采买方数量越多&#xff0c;可以将广告库存卖掉。但实际的广告变现业务中&#xff0c;100%的广告填充率几乎无法达成。…

微软编程之C#如何学习,C#学习路线:从入门到精通

引言 C# 是一种由微软开发的面向对象编程语言&#xff0c;广泛应用于 Windows 应用程序开发、游戏开发&#xff08;Unity&#xff09;、Web 开发&#xff08;ASP.NET&#xff09;等领域。对于初学者来说&#xff0c;掌握 C# 不仅能够打开编程世界的大门&#xff0c;还能为未来…

基于Ubuntu Ollama 部署 DeepSeek-R132B 聊天大模型(附带流式接口调用示例)

最近 DeepSeek出来了&#xff0c;很火&#xff0c;说是能跟ChatGpt o1 媲美&#xff0c;结果&#xff0c;用了DeepSeek的官方服务&#xff0c;提示“服务器繁忙 请稍后再试。”&#xff0c;我就想&#xff0c;算了&#xff0c;自己部署个吧。 我这个是基于docker部署的&#x…

深入理解QT的View-Model-Delegate机制和用法

文章目录 Model-View-Delegate机制Model(数据模型)设置模型属性访问元素操作元素数据排序封装好的模型View(视图)显示数据数据选择Delegate(代理)数据选择易用封装类QListWidgetQTreeWidgetQTableWidget元素拖拽代理模型参考示例Model-View-Delegate机制 Qt的View/Model/Deleg…

计算机毕业设计Python+Spark知识图谱医生推荐系统 医生门诊预测系统 医生数据分析 医生可视化 医疗数据分析 医生爬虫 大数据毕业设计 机器学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

GIS笔记之Shapefile与KML相互转换

在GIS应用中&#xff0c;各种不同类型数据的转换与使用是一个重要的环节。在这其中&#xff0c;Shapefile和KML是两种常见的数据形式&#xff0c;两者间的相互转换也是日常工作和学习绕不开的话题。在这里&#xff0c;我们将常用的几种数据转换方法整理如下。 1.Shapefile和KM…

VBA语言的软件工程

VBA语言的软件工程 引言 在当今信息化时代&#xff0c;软件工程已经成为了一门重要的学科&#xff0c;它涉及到软件的设计、开发、测试和维护等多个环节。而在众多编程语言中&#xff0c;Visual Basic for Applications&#xff08;VBA&#xff09;凭借其易学易用的特点&…

Java 读取 Word 模板文档并替换内容生成新文档

嘿&#xff0c;朋友们&#xff01;在实际开发中&#xff0c;经常会遇到需要根据 Word 模板生成特定文档的需求&#xff0c;比如合同、报告等。咱们可以使用 Apache POI 库来读取 Word 模板文档&#xff0c;然后替换其中的指定内容&#xff0c;最后生成新的文档。下面我就详细给…