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

目录

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;

    @Before
    public 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);
    }

    @Test
    public void addExamination(){
        when(examinationDao.insert(any())).thenReturn(1);
        Examination examination = new Examination();
        examination.setKeyId(UUID.randomUUID()+"");
        Assert.assertEquals(examinationBaseSvr.addExamination("",examination),examination);
    }

    @Test
    public 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);
    }

    @Test
    public void modifyExamination(){
        when(examinationDao.update(any())).thenReturn(1);
        Examination examination = new Examination();
        examination.setKeyId(UUID.randomUUID()+"");
        Assert.assertEquals(examinationBaseSvr.modifyExamination("",examination),examination);
    }

    @Test
    public 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);
    }

    @Test
    public 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);
    }

    @Test
    public 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的内容:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/654053.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

solidworks 3D草图案例2-方块异形切

单位mm 单位mm 长方体 底面是48mm*48mm&#xff0c;高为60mm 3D草图 点击线&#xff0c;根据三视图&#xff0c;绘制角度线&#xff0c; 由于三点确定一个面&#xff0c;因此确定三点就可以了 基准面 点击参考几何体-基准面&#xff0c;依次点击3个点 曲面切除 完成后点击插…

海外仓ERP系统:赋能海外仓,实现标准化管理

随着业务规模的不断发展和业务类型的复杂度逐渐提升&#xff0c;传统的海外仓管理模式已经很难适应现在的情况了。对海外仓企业来说&#xff0c;一套合适的海外仓管理erp系统可以起到很大的辅助作用。 不过很多小型海外仓企业会纠结于是同时选择企业erp系统和海外仓管理系统&a…

重庆耶非凡科技有限公司的选品师项目靠谱吗?

在跨境电商和零售市场日益繁荣的今天&#xff0c;选品师的角色愈发凸显出其重要性。重庆耶非凡科技有限公司作为一家致力于多元化服务的科技公司&#xff0c;其选品师项目备受关注。那么&#xff0c;重庆耶非凡科技有限公司的选品师项目靠谱吗?接下来&#xff0c;我们将从多个…

网上3d全景虚拟交互展馆沉浸式体验让客户和使用者都满意

在数字化浪潮席卷而来的今天&#xff0c;3D场景网站已成为众多行业展现创意与实力的新舞台。然而&#xff0c;传统的3D建模软件往往因其复杂性和高门槛&#xff0c;让许多渴望创建逼真3D场景的用户望而却步。 幸运的是&#xff0c;华锐视点推出了搭建3D场景网站的编辑器——一款…

双机多网口配置同网段地址,可以通过目的IP确定接收数据的网卡吗?

环境 两台机器两网卡同网段接入同一个二层交换机。 机器A ens38 00:0c:29:a4:8b:fb 10.0.0.11/24 ens39 00:0c:29:a4:8b:05 10.0.0.12/24 机器B ens38 00:0c:29:4f:a6:c4 10.0.0.21/24 ens39 00:0c:29:4f:a6:ce 10.0.0.22/24 初始ARP表 只有管理口接口的ARP表项&#xff0c…

Vue2+Element 封装评论+表情功能

有需要的小伙伴直接拿代码即可&#xff0c;不需要下载依赖。 评论组件如下&#xff1a; 创建 comment.vue 文件。 表情组件 VueEmoji.vue 在评论组件中使用。 <template><div class"comment"><div class"flex_box"><h2>评论 {…

Amazon云计算AWS(一)

目录 一、基础存储架构Dynamo&#xff08;一&#xff09;Dynamo概况&#xff08;二&#xff09;Dynamo架构的主要技术 二、弹性计算云EC2&#xff08;一&#xff09;EC2的基本架构&#xff08;二&#xff09;EC2的关键技术&#xff08;三&#xff09;EC2的安全及容错机制 提供的…

【C++】vector的基本使用

&#x1f497;个人主页&#x1f497; ⭐个人专栏——C学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 目录 导读 1. vector初始化 2. vector iterator 的使用 2.1 常规遍历 2.2 rbegin rend 反向遍历 3. vector 常见函数使用 3.1 …

N1912A P 系列双通道功率计

N1912A 双通道功率计 产品综述 <<<P 系列双通道功率计>>> Keysight N1912A P 系列双通道功率计可以提供峰值功率、峰均比、平均功率、上升时间、下降时间 NS 脉冲宽度测量。 “ 功能特点 30 MHz 视频带宽 能够以高达 100 MSa/s 的采样率执行单次实时捕…

全新交友盲盒+付费进群二合一源码 包含全套源码+教程

盲盒交友脱单系统源码&#xff0c;带教程&#xff0c;免授权这套源码已经替你们搭建测试过了 附带进群系统&#xff0c;定位是正常的 申明需要无限回调&#xff0c;没有回调的搭建出来不能用不要说源码不能用 全新系统方便大家使用&#xff0c;已经录制好详细的教程&#xf…

AIGC 007-E4T基于编码器的域调优用于文本到图像模型的快速个性化!

AIGC 007-E4T基于编码器的域调优用于文本到图像模型的快速个性化&#xff01; 文章目录 0 论文工作1 论文方法2 效果 0 论文工作 这篇论文提出了一种使用领域特定编码器来快速将文本到图像模型适配到新领域的方案。这种被称为基于编码器的领域微调 (E4T) 的方法&#xff0c;专…

不怕YOLOv10高歌猛进,我有YOLOv8稳扎稳打

YOLOv10 出来有几天时间了&#xff0c;这次我没有选择第一时间出文章解析&#xff0c;如此频繁的发布数字版本的 YOLO 着实让人头疼&#xff0c;虽然数字的更新并非旧版技术的过时&#xff0c; 但是这肯定会让很多在校同学增加很多焦虑情绪。这里还是请大家辩证看待。 v10 这次…

C语言编译链接

翻译环境和运行环境 翻译环境 翻译环境就是在这将源代码转换成可执行的二进制指令&#xff08;机器指令&#xff09;。 进行编译和链接过程。 .c源程序先单独经过编译器生成对应的目标文件.obj&#xff08;在windows环境下&#xff09;.o&#xff08;在Linux环境下&#xff…

CRMEB开源商城系统:全开源、高灵活性的电商解决方案

一、引言 随着电子商务的飞速发展&#xff0c;越来越多的企业和个人开始关注如何快速搭建一个稳定、高效且功能丰富的在线商城系统。在这样的背景下&#xff0c;CRMEB开源商城系统应运而生&#xff0c;凭借其前后端分离的架构、丰富的功能模块以及易用性&#xff0c;成为了众多…

C++模板——非类型模板参数、模板的特化以及模板的分离编译

目录 非类型模板参数 模板的特化 概念 函数模板特化 类模板特化 全特化 偏特化 模板的分离编译 什么是分离编译 模板的分离编译 解决方法 模板总结 非类型模板参数 模板参数可分为类型形参和非类型形参。类型形参&#xff1a; 出现在模板参数列表中&#xff0c;跟…

【python】删除一个列表中的所有的1

删除所有的1 x [1, 1, 6, 3, 9, 4, 5, 1, 1, 2, 1, 9, 6, 4] 使用lambda函数和filter来过滤掉x中的1 filtered_x list(filter(lambda n: n ! 1, x)) 不是1的数字&#xff0c;存进x列表&#xff0c;filter用于插入元素到第二个位置 print(filtered_x) # 输出: [6, 3, 9, …

第13章 层次式架构设计理论与实践

层次式架构的核心思想是将系统组成为一种层次结构&#xff0c;每一层为上层服务&#xff0c;并作为下层客户。其实不管是分层还是其他的架构都是为了解耦&#xff0c;更好的复用&#xff0c;只要秉承着这种思想去理解一切都迎刃而解了。 13.1 层次上体系结构概述 回顾一下软件…

【docker】安装harbor出现问题: Running 1/1 ✘ Network harbor_harbor Error

安装harbor出现问题&#xff1a; [] Running 1/1 ✘ Network harbor_harbor Error 0.2s failed to create network harbor_harbor: Error response from daemon: Fa…

节水“云”科普丨北京昌平VR节水云展馆精彩上线

2024年5月15日上午&#xff0c;由北京昌平区水务局主办的“推进城市节水&#xff0c;建设美丽昌平——2024年全国城市节约用水宣传周暨‘坚持节水优先 树立节水标杆’昌平节水在行动主题实践活动”隆重举办&#xff0c;活动期间&#xff0c;昌平区水务局应用VR虚拟现实技术创新…

目标检测数据集 - 工地工人安全设备佩戴检测数据集下载「包含VOC、COCO、YOLO三种格式」

数据集介绍&#xff1a;工地工人安全设备佩戴检测数据集&#xff0c;真实场景数据生成增强后高质量图片数据&#xff0c;涉及场景丰富&#xff0c;比如楼宇建筑工地工人作业数据、道路建筑工地工人作业数据、室内工地工人作业数据、露天挖掘场景工人作业数据、工地工人自拍摆拍…