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

相关推荐
Chenyiax2 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH2 小时前
Koa和Express的区别
后端
MariaH2 小时前
Koa框架的使用
后端
luckdewei3 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某4 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy4 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom4 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github
用户1474853079749 小时前
CodeX使用Skill生成游戏美术和音乐资源,一分钟入门
后端
Melody1239 小时前
用 abort 中断 AI 流式请求,我之前做错了
后端
onething3659 小时前
Spring Boot + Spring AI 从入门到实战:7天转型计划 Day 5 —— SSE 流式输出 + 打字机效果
人工智能·后端·全栈