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

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

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


目录
相关文章
|
1月前
|
测试技术 持续交付 UED
软件测试的艺术:确保质量的实战策略
在软件开发的舞台上,测试是那把确保每个功能如交响乐般和谐奏响的指挥棒。本文将深入探讨软件测试的重要性、基本类型以及如何设计高效的测试策略。我们将通过一个实际的代码示例,展示如何运用这些策略来提升软件质量和用户体验。
|
2月前
|
Java 测试技术 开发者
必学!Spring Boot 单元测试、Mock 与 TestContainer 的高效使用技巧
【10月更文挑战第18天】 在现代软件开发中,单元测试是保证代码质量的重要手段。Spring Boot提供了强大的测试支持,使得编写和运行测试变得更加简单和高效。本文将深入探讨Spring Boot的单元测试、Mock技术以及TestContainer的高效使用技巧,帮助开发者提升测试效率和代码质量。
281 2
|
16天前
|
安全 Java 测试技术
springboot之SpringBoot单元测试
本文介绍了Spring和Spring Boot项目的单元测试方法,包括使用`@RunWith(SpringJUnit4ClassRunner.class)`、`@WebAppConfiguration`等注解配置测试环境,利用`MockMvc`进行HTTP请求模拟测试,以及如何结合Spring Security进行安全相关的单元测试。Spring Boot中则推荐使用`@SpringBootTest`注解简化测试配置。
|
24天前
|
Java 测试技术 API
详解Swagger:Spring Boot中的API文档生成与测试工具
详解Swagger:Spring Boot中的API文档生成与测试工具
35 4
|
1月前
|
JSON Java 测试技术
SpringCloud2023实战之接口服务测试工具SpringBootTest
SpringBootTest同时集成了JUnit Jupiter、AssertJ、Hamcrest测试辅助库,使得更容易编写但愿测试代码。
60 3
|
2月前
|
机器学习/深度学习 编解码 监控
目标检测实战(六): 使用YOLOv8完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)
这篇文章详细介绍了如何使用YOLOv8进行目标检测任务,包括环境搭建、数据准备、模型训练、验证测试以及模型转换等完整流程。
3072 1
目标检测实战(六): 使用YOLOv8完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)
|
1月前
|
缓存 测试技术 Apache
告别卡顿!Python性能测试实战教程,JMeter&Locust带你秒懂性能优化💡
告别卡顿!Python性能测试实战教程,JMeter&Locust带你秒懂性能优化💡
53 1
|
1月前
|
前端开发 数据管理 测试技术
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第27天】本文介绍了前端自动化测试中Jest和Cypress的实战应用与最佳实践。Jest适合React应用的单元测试和快照测试,Cypress则擅长端到端测试,模拟用户交互。通过结合使用这两种工具,可以有效提升代码质量和开发效率。最佳实践包括单元测试与集成测试结合、快照测试、并行执行、代码覆盖率分析、测试环境管理和测试数据管理。
59 2
|
1月前
|
前端开发 JavaScript 数据可视化
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第26天】前端自动化测试在现代软件开发中至关重要,Jest和Cypress分别是单元测试和端到端测试的流行工具。本文通过解答一系列问题,介绍Jest与Cypress的实战应用与最佳实践,帮助开发者提高测试效率和代码质量。
43 2
|
25天前
|
Java 测试技术 数据库连接
使用Spring Boot编写测试用例:实践与最佳实践
使用Spring Boot编写测试用例:实践与最佳实践
57 0
下一篇
DataWorks