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

news2024/11/20 23:14:39

目录

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

conda 环境找不到 libnsl.so.1

安装prokka后运行报错 perl: error while loading shared libraries: libnsl.so.1: cannot open shared object file: No such file or directory 通过conda list 可以看到 有libsnl 2.00版本&#xff0c;通过修改软链接方式进行欺骗

K8s集群中的Pod调度约束亲和性与反亲和性

前言 在 K8s 集群管理中&#xff0c;Pod 的调度约束——亲和性&#xff08;Affinity&#xff09;与反亲和性&#xff08;Anti-Affinity&#xff09;这两种机制允许管理员精细控制 Pod 在集群内的分布方式&#xff0c;以适应多样化的业务需求和运维策略。本篇将介绍 K8s 集群中…

【软考】设计模式之外观模式

目录 1. 说明2. 应用场景3. 结构图4. 构成5. 适用性6. 优点7. 缺点8. java示例 1. 说明 1.为子系统中的一组接口提供一个一致的界面&#xff0c;Facade模式定义了一个高层接口&#xff0c;这个接口使得这一子系统更加容易使用。2.Facade Pattern。3.是一种结构型设计模式&…

OM电商系统asp.net

OM电商系统&#xff0c;可以让顾客全面了解商品的详细信息&#xff0c;消除网上购物的信息不对称问题。通过商品分类来组织众多的商品&#xff0c;方便顾客找到所需要的商品。提供客服顾客互动机制&#xff0c;提高顾客的参与度。通过设计合理的订单处理流程&#xff0c;提高顾…

JavaSE:String类常用方法(巨详细)

1、引言 在学习C语言时&#xff0c;我们就已经了解了字符串&#xff0c;但是在C语言中字符串只能用字符数组或者指针来表示&#xff0c;没有特定的字符串类型&#xff0c;而在Java中&#xff0c;我们有了字符串这一特定的类型---String类。 2、String类的常用方法 2.1 字符串…

第15章-超声波避障功能 HC-SR04超声波测距模块详解STM32超声波测距

这个是全网最详细的STM32项目教学视频。 第一篇在这里: 视频在这里 STM32智能小车V3-STM32入门教程-openmv与STM32循迹小车-stm32f103c8t6-电赛 嵌入式学习 PID控制算法 编码器电机 跟随 15.1-超声波测距 完成超声波测距功能、测量数据显示在OLED屏幕上 硬件介绍 使用&#…

OpenHarmony实战开发——宿舍全屋智能开发指南

项目说明 基于OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09;、数字管家开发宿舍全屋智能&#xff0c;实现碰一碰开门、碰一碰开灯、碰一碰开风扇以及烟感检测。因为各项目开发流程大体相似&#xff0c;本文主要以碰一碰开门为例介绍如何在现有OpenHar…

野外作战武器操作3D模拟实操仿真训练以便老兵能适应不同的训练需求

强国必须强军&#xff0c;我国在军事方面的投入持续加大&#xff0c;自然在军事武器培训方面不容忽视&#xff0c;在军事领域&#xff0c;3D模拟展示不仅提升了军事训练的效率&#xff0c;还为我们提供了更加直观、真实的武器体验。 首先&#xff0c;3D军事武器模拟展示能够提供…

Django革新者:突破传统,构建下一代Web应用

书接上文 —— 家园建筑师&#xff1a;用Django打造你的Web帝国&#xff0c;从前面的学习中&#xff0c;咱们我们经历了一个完整的Django Web开发之旅&#xff0c;涵盖了从基础概念到高级特性的各个方面&#xff1a; 引言&#xff1a;介绍了企业级Web框架的需求&#xff0c;并概…

分享个自用的 Nginx 加强 WordPress 防护的规则

Nginx WordPress 的组合是目前非常普及的组合了&#xff0c;我们完全可以借助 Nginx 规则来加强 WordPress 的防护&#xff0c;提高 WordPress 的安全性&#xff0c;今天明月就给大家分享个自用的 Nginx 针对 WordPress 的防护规则&#xff0c;部分规则大家只需要根据自己的需要…

ABB焊接功能介绍

1.基本配置 1.2配置Robot Ware Arc 2.焊接语句 2.1直线焊接语句 过渡点指令必须位于起弧指令与熄弧指令之间&#xff0c;不能单独使用。 2.2直线焊接示例 2.3圆弧焊接语句 2.4圆弧焊接示例 2.5摆动参数 关于ABB焊接机器人摆动参数设定 一般情况下&#xff0c;主要设置以…

[Linux]磁盘管理

一.Linux磁盘管理的原理 磁盘分区与Linux的目录是借助"挂载机制"链接的&#xff0c;将一个分区与一个目录连接起来。访问目录&#xff0c;相当于访问某块分区 lsblk命令: lsblk命令可以查看磁盘分区&#xff0c;以及每个分区所挂载的目录 lsblk -f 可以查看更细节的…

利用NewGIS平台将FME模板发布为接口

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 一、模板编写 二、发布模板 三、接口获取 四、移动端调用 ​​​​​ 前言 在实际的应用生产过程中&#xff0c;尤其是移动端GIS软件的开发&#xff0c;针对一些闭…

win11中文文件名称乱码解决方案

解压后出现以下的乱码 解决方案 步骤1.winR 输入intl.cpl 或 windows 自带的搜索搜“intl.cpl”&#xff0c;打开这个面板控制项 步骤2.在新打开的区域面板中&#xff0c;选择“管理”标签页&#xff0c;点击下方的“更改系统区域设置”按钮 步骤3.取消"Beta版…"选…

Nginx的配置与调试

目录 1、安装Nginx 2、Nginx的配置文件结构 2.1 Nginx的全局配置 2.2 HTTP服务器配置 2.3 HttpGzip模块配置 2.4 负载均衡配置 2.5 server虚拟主机配置 2.6 location URL匹配配置 2.7 StubStatus模块配置 1、安装Nginx 在安装Nginx之前&#xff0c;需确保系统已经安装…

Day38 代码随想录打卡|二叉树篇---二叉树的最大深度

题目&#xff08;leecode T104&#xff09;&#xff1a; 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 递归法&#xff1a; 递归法首先将根节点作为参数传入&#xff0c;随后每一轮传入当前节点的…

【openlayers系统学习】3.3假彩色图像合成(三个波段合成假彩色图像)

三、假彩色图像合成 在上一步中&#xff0c;我们使用 ol/source/GeoTIFF​ 源从单个多波段源&#xff08;具有红色、绿色、蓝色和Alpha波段&#xff09;渲染真彩色图像。在下面这个例子中&#xff0c;我们将从可见光谱之外提取数据&#xff0c;并使用它来呈现假彩色合成。 我…

Linux echo命令(在终端输出文本)

文章目录 Linux Echo命令深度解析简介命令语法常见选项- -n&#xff1a;不输出行尾的换行符&#xff0c;这意味着输出后不会换到下一行。- -e&#xff1a;启用反斜杠转义的解释&#xff0c;允许使用特殊字符。- -E&#xff1a;禁用反斜杠转义的解释&#xff08;默认选项&#x…

简单得阴影引导实现

效果如下: 实现方式&#xff1a; 1、引入三方库&#xff1a; implementation io.github.razerdp:BasePopup:3.2.0 2、代码实现 class NewUserGuide3Popup : BasePopupWindow {constructor(activity: Activity) : super(activity)constructor(context: Context) : super(con…

如何开启tomcat管理功能

1 、修改conf/tomcat-users.xml <role rolename"manager-gui"/> <user username"tomcat" password"tomcat" roles"manager-gui"/>2、修改webapps/manager/META-INF/context.xml <Valve className"org.apache.cata…