基于SpringBoot实现单元测试的多种情境/方法(二)
本文分享自天翼云开发者社区@《基于SpringBoot实现单元测试的多种情境/方法(二)》, 作者:才开始学技术的小白
1 Mock基础回顾
在上一篇分享中我们详细介绍了简单的、用mock来模拟接口测试环境的方法,具体的使用样例我们再回顾一下:
1.首先是最简单的不需要传参的示例,需要注意的是,可能@Resource这个注解识别不了,没关系,换成@Autowired通常是等效的
//进行每一次mock模拟tomcat容器的时候,使用随机端口启动,这样不会有端口占用的问题@SpringBootTest(classes = {Springboot01Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)//自动配置以及启用mvc对象@AutoConfigureMockMvcpublic class MockMVCTester {
//注入MockMVC对象,它是springtest依赖中自带的
@Resource
private MockMvc mockMvc;
@Test
public void testMock() throws Exception {
//获取mock返回的对象
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/user"))//perform模拟一个http请求,这里是get方法
.andExpect(MockMvcResultMatchers.status().isOk())//添加预期,如果服务器返回的是200
.andDo(MockMvcResultHandlers.print())//那我们就把请求和响应的信息在控制台中打印输出
.andExpect(MockMvcResultMatchers.content().string("[{\"uid\":1001,\"uname\":\"wu\"," +
"\"password\":\"1212\",\"addrs\":[\"nanchang\",\"sichuan\",\"beijing\"]}," +
"{\"uid\":1002,\"uname\":\"du\",\"password\":\"1313\",\"addrs\"" +
":[\"chang\",\"sica\",\"beng\"]}]"))//content表示对于返回的请求体数据进行判断,string表示进行比对
.andReturn();//将结果返回出来
}}
2.如果get方法需要传参,通常是在query中(也就是问号传参的形式),也有可能是正常传参,而且这种通常有json返回,正常传参的示例如下(.param也可以改为.params);如果是query传参,改用.queryparam即可
@Test@DisplayName("get方法+有入参+有json返回")public void testMock1() throws Exception {
//mock返回的对象可以不获取,因为单纯的判断对错用不上
mockMvc.perform(MockMvcRequestBuilders.get("/user/para")//perform模拟一个http请求,这里是get方法
.header("token", "akakak")//请求头
.param("id","wy")//请求参数
.param("password","asd"))//请求参数
.andExpect(MockMvcResultMatchers.status().isOk())//添加预期,如果服务器回的是200
.andDo(MockMvcResultHandlers.print())//那我们就把请求和响应的信息在控制台中打印输出
.andExpect(MockMvcResultMatchers.jsonPath("ak").value("asd"))//获取返回的json并核对对应的值是否一样
.andReturn();//将结果返回出来}
3.最后就是post方法的常用body传参,一般都是json格式,示例如下;这里用到了IoC创建对象,不了解的读者可以看看我专栏的IoC相关分享
@Test@DisplayName("post方法测试用例")public void testMock1() throws Exception {
//IoC
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("IoC.xml");
User user = context.getBean(User.class);
ObjectMapper mapper = new ObjectMapper();
user.setUname("wy");
//mock返回的对象可以不获取,因为单纯的判断对错用不上
mockMvc.perform(MockMvcRequestBuilders.post("/user")//perform模拟一个http请求,这里是get方法
.content(mapper.writeValueAsString(user))//用IoC建立一个User对象
.contentType(MediaType.APPLICATION_JSON_VALUE))//添加json类数据,转化为入参
.andExpect(MockMvcResultMatchers.status().isOk())//添加预期,如果服务器回的是200
.andDo(MockMvcResultHandlers.print())//那我们就把请求和响应的信息在控制台中打印输出
.andExpect(MockMvcResultMatchers.jsonPath("uname").value("wy"))//获取返回的json并核对对应的值是否一样
.andReturn();//将结果返回出来}
2 Mock的进阶用法
当然了,Mock不可能只有这些模拟接口的简单用法,本文就介绍一写些其他的常用模式
2.1 简便创建单元测试
如果你使用的IDE和我一样,是IDEA,那么你可以通过在需要写单元测试的方法上Ctrl+Shift+T来快捷创建
之后你当然可以选择是否增加运行在测试之前的@Before、运行在测试之后的@After(即Junit的相关注解,在这里不赘述)
注:可以自行查阅JUnit文档:https://junit.org/junit5/docs/current/user-guide/
2.2 @Mock、@Spy、@InjectMocks
有的时候我们需要测试的单元——比如说某一个类,可能依赖于其他的类,而且这个被依赖的类往往不是很好构造,因为他们可能又依赖于其他的类、库、底层资源等等,这个时候mock就帮上了大忙:
1.创建测试用的类
同样我们来举例说明一下,假设我们有一个UserInfo类存储了用户信息,他长这个样子:
public class UserInfo {
private UserRepository userRepository;
private UserWord userWord;
public String getUserAddr(){
return userRepository.getUsrAddr();
}}
非常简单的一个UserInfo类,可以看到他依赖于两个子类,一个是用户数据库(UserRepository),一个是用户词(UserWord)
**都是随便编的哈不要在意**
这两个子类长这个样子:
@Datapublic class UserWord {
private String word1;
private String word2;}
@Datapublic class UserRepository {
private String repUUID;
private String usrPassword;
private String usrAddr = "Sichuan";}
也一样非常简单,但大家可以看到我们把UserRepository的usrAddr这个属性赋了一个默认值“Sichuan”,为什么呢?就是为了解释@Spy和@Mock的作用
2.几个重要注解的定义:
@Mock 非真实执行,用来模拟在测试中不好创建的类
@Spy 真实执行,用来模拟需要真实执行的类
@InjectMocks 真实执行,针对实现类使用,不能作用在接口上
大家可能看的一头雾水,但其实用我们刚刚的UserInfo示例来解释,就非常简单:
UserRepository这个类,虽然在我们这里很简单,但我们把他当做一个数据库类来理解——也就是说,在真实开发中,这样的类在单元测试中是不好处理的,因为你不可能为了测试UserInfo这个类的方法,去专门建一个数据库,这个太麻烦了。
那么@Mock就起到了这么一个作用,他相当于给所注解的实例套了一个外壳,我们不用关心里面是怎么样的,系统将其全部设置为null
相当于我们在单元测试的时候,不管@Mock所在的类究竟是什么样子,也不需要专门为了他去建数据库、建依赖。
这就是Mock的核心作用——依赖解耦,尤其是在单元测试中,我们要斩断复杂的类依赖关系,专心测试某一块的功能。
那么@Spy就很好理解了,有些我们需要用到其中功能的类,或者说比较简单的类,就用@Spy来注入,@Mock和@Spy都是将实例注入到@InjectMocks所标注的地方,用一段测试代码来展示这一点:
@SpringBootTest(classes = {Springboot01Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)public class MockTest2 {
@Mock
private UserRepository userRepository;
@Spy
private UserWord userWord;
@InjectMocks
private UserInfo userInfo;
@Test
public void test1(){
Assertions.assertEquals("Sichuan",userInfo.getUserAddr());
}}
可以看到,我们并没有专门创建UserInfo的子类,而是直接用了注入的办法;这其实跟Spring本身的@Component+@Autowired的方法有点类似,
这里如果去运行test1,是不会通过的;因为UserRepository使用@Mock来注入的,里面的东西都是null,如果改成@Spy,测试就可以通过了。
2.3 桩
有一种情况:UserRepository不好创建所以我们用Mock模拟一个,但万一我还是想要用里面的某个方法呢?万一UserInfo的某个方法依赖于UserRepository的某一个方法的返回呢?这种情况我显然不能再返回null了,所以“桩”就用在了这种情况:
桩函数(stub):使用一些自己定义的测试函数来替换当前需要测试的函数。被替换的函数可能是目前还没写完的,这样能够加速开发,或更好的找错误源。
打桩(存根):模拟要调用的函数(打桩对象),给它提供桩函数,给桩函数返回一个值。简单的说自定义输入输出,不打桩默认返回null。
也就是说,我可以让UserRepository去打桩来返回一个我需要的值!!!
这样一来就非常方便了,举一个非常常用且经典的例子:
@Test
public void test1(){
Mockito.when(userRepository.getUsrAddr()).thenReturn("Beijing");
Assertions.assertEquals("Beijing",userInfo.getUserAddr());
}
可以看到,新的test1如上,when...thenReturn方法设置了这个Mock对象的方法被调用的时候应该返回一个什么样的值,即我们自定义了Mock方法的出入参(当然了这里没有入参),这个测试案例是可以通过的,因为方法被调用的时候不再返回默认值null了
when...thenReturn就是我们的桩函数,
Mockito.when(userRepository.getUsrAddr()).thenReturn("Beijing");就是我们的打桩过程
实际上Mockito库有很多方法可以供我们调用,即使是@Spy注入的类也可以使用,大家可以自行去查手册,或者参考下表:
3 Mock也有解决不了的情况
很多开发都会接触到Linux系统,如果有些功能是给Linux写的,集成测试、系统测试就需要去搭建一套测试环境,但怎么去做单元测试呢?单元测试就应该尽可能的简单和全面,搭建一套这个环境也太麻烦了吧
没办法这次还真得搭,Mock也模拟不了虚拟机
我们是可以在IDEA中借用一些xshell来使用bash命令,但真正要跑测试,比较方便的还是自己搭一台虚拟机出来,如果有不熟悉的可以关注我的Linux专栏,有搭建虚拟机的经验分享。
搭建并配网好了之后需要给虚拟机安装Java,这个过程就比较简单了,贴个链接的大家可以参考:https://blog.csdn.net/wcy1900353090/article/details/125121855
安装好了之后就可以按如下流程用IDEA链接虚拟机进行测试:
基于SpringBoot实现单元测试的多种情境/方法(二)的更多相关文章
- SpringBoot自定义装配的多种实现方法
Spring手动装配实现 对于需要加载的类文件,使用@Configuration/@Component/@Service/@Repository修饰 @Configuration public cla ...
- 基于spring-boot的应用程序的单元测试方案
概述 本文主要介绍如何对基于spring-boot的web应用编写单元测试.集成测试的代码. 此类应用的架构图一般如下所示: 我们项目的程序,对应到上图中的web应用部分.这部分一般分为Control ...
- SpringBoot系列: 单元测试2
之前发了SpringBoot 单元测试的博客, https://www.cnblogs.com/harrychinese/p/springboot_unittesting.html , 内容较少, 现 ...
- 基于spring-boot的应用程序的单元+集成测试方案
目录 概述 概念解析 单元测试和集成测试 Mock和Stub 技术实现 单元测试 测试常规的bean 测试Controller 测试持久层 集成测试 从Controller开始测试 从中间层开始测试 ...
- SpringData 基于SpringBoot快速入门
SpringData 基于SpringBoot快速入门 本章通过学习SpringData 和SpringBoot 相关知识将面向服务架构(SOA)的单点登录系统(SSO)需要的代码实现.这样可以从实战 ...
- 基于SpringBoot+SSM实现的Dota2资料库智能管理平台
Dota2资料库智能管理平台的设计与实现 摘 要 当今社会,游戏产业蓬勃发展,如PC端的绝地求生.坦克世界.英雄联盟,再到移动端的王者荣耀.荒野行动的火爆.都离不开科学的游戏管理系统,游戏管理系 ...
- 消息队列1:RabbitMQ解析并基于Springboot实战
RabbitMQ简介 AMQP:Advanced Message Queue,高级消息队列协议.它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产 ...
- 基于springboot的web项目最佳实践
springboot 可以说是现在做javaweb开发最火的技术,我在基于springboot搭建项目的过程中,踩过不少坑,发现整合框架时并非仅仅引入starter 那么简单. 要做到简单,易用,扩展 ...
- JeecgBoot 2.1.1 代码生成器AI版本发布,基于SpringBoot+AntDesign的JAVA快速开发平台
此版本重点升级了 Online 代码生成器,支持更多的控件生成,所见即所得,极大的提高开发效率:同时做了数据库兼容专项工作,让 Online 开发兼容更多数据库:Mysql.SqlServer.Ora ...
- MyBatis 进阶,MyBatis-Plus!(基于 Springboot 演示)
这一篇从一个入门的基本体验介绍,再到对于 CRUD 的一个详细介绍,在介绍过程中将涉及到的一些问题,例如逐渐策略,自动填充,乐观锁等内容说了一下,只选了一些重要的内容,还有一些没提及到,具体可以参考官 ...
随机推荐
- Python 错误:ModuleNotFoundError: No module named 'conf'
问题描述: 编译器无法使用cmd命令来执行py文件,结果可能报ModuleNotFoundError的错误. 比如在cmd任务栏执行:python E:\myProgram\Python\spider ...
- LOJ数列分块入门九题(上)
一转眼,已经有整整一年没有在博客园写博客了.去洛谷写了几篇(How time flys. 最近突然想起其实我不太擅长分块算法,又想起去年暑假有位同学同我提起过LOJ的数列分块九题,说来惭愧,打了这么久 ...
- 2.5基本算法之搜索 156:LETTERS
#include<iostream>#include<cstdio>using namespace std;char a[21][21];bool b[26]; int X[4 ...
- 页面与java后台之 上传文件与服务器加载
添加相关依赖包commons-fileupload等: 页面(注:编码enctype="multipart/form-data" 文件按钮属性multiple) java ...
- cmd 下运行pyhon文件.py
第一步: wind+R打开[运行],输入cmd,点击确定 第二步: ①输入:[cd]指定pyhon文件目录 ② cd C:\Users\pc\Desktop\test ③在指定目录下输入pyhon文件 ...
- 如何设置表格的高度 element 的table 组件
<el-table :row-style="{ height: '30px' }" :cell-style="{ padding: 0 }" :data= ...
- MySQL如何指定字符集和排序规则?
在MySQL中,可以使用以下两种方式指定字符集和排序规则: 创建数据库或表时指定字符集和排序规则 在创建数据库或表时,可以使用 CHARACTER SET 和 COLLATE 选项指定字符集和排序规则 ...
- 问道Golang,6月龄必知必会(二)
在我看来,golnag有许多反直观的设计,而且这些设计通常不能自圆其说,导致gohper一而再再而三的调入陷阱. 网上也有很多gohper总结了一些笔记,我再提炼精简一下,挂在脑图树上便于记忆. 值类 ...
- Python批量采集百度资讯文章,如何自定义采集日期范围
01 引言 大家好!蜡笔小曦有个朋友是做能源相关工作的,她想要有一个工具以天为单位持续地采集百度资讯中能源相关的文章进行留存和使用. 其中有个需求点是说能够自定义采集的开始日期和结束日期,这样更加灵活 ...
- Kattis mapcolouring(状压dp)
刚知道vj上查看别人代码,看不到汉字...我理解的都注明后边了. #include <bits/stdc++.h> #define ll long long #define met(a, ...