基于SpringBoot实现单元测试的多种情境/方法(二)

天翼云开发者社区
• 阅读 343

本文分享自天翼云开发者社区@《基于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来快捷创建 基于SpringBoot实现单元测试的多种情境/方法(二)

之后你当然可以选择是否增加运行在测试之前的@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注入的类也可以使用,大家可以自行去查手册,或者参考下表: 基于SpringBoot实现单元测试的多种情境/方法(二)

3 Mock也有解决不了的情况 很多开发都会接触到Linux系统,如果有些功能是给Linux写的,集成测试、系统测试就需要去搭建一套测试环境,但怎么去做单元测试呢?单元测试就应该尽可能的简单和全面,搭建一套这个环境也太麻烦了吧 没办法这次还真得搭,Mock也模拟不了虚拟机 我们是可以在IDEA中借用一些xshell来使用bash命令,但真正要跑测试,比较方便的还是自己搭一台虚拟机出来,如果有不熟悉的可以关注我的Linux专栏,有搭建虚拟机的经验分享。 搭建并配网好了之后需要给虚拟机安装Java,这个过程就比较简单了,贴个链接的大家可以参考:https://blog.csdn.net/wcy1900353090/article/details/125121855 安装好了之后就可以按如下流程用IDEA链接虚拟机进行测试:

基于SpringBoot实现单元测试的多种情境/方法(二) 基于SpringBoot实现单元测试的多种情境/方法(二)

点赞
收藏
评论区
推荐文章
创建本地yum仓库
本文分享自天翼云开发者社区《》,作者:zzzzgj;背景有的部署环境不通外网,但希望继续使用yum命令下载依赖包而不修改部署脚本逻辑。因此记录一个本地repo的建立方法。1、获取依赖包如在通网的机器上下载openssldevel所有依赖yumdownloa
Springfox与SpringDoc——swagger如何选择(SpringDoc入门)
本文分享自天翼云开发者社区@《》,作者:才开始学技术的小白0.引言之前写过一篇关于swagger(实际上是springfox)的使用指南(https://www.ctyun.cn/developer/article/371704742199365),涵盖了
如何计算真实的数据库成本
本文分享自天翼云开发者社区《》作者:王乾在云计算占主导地位之前,计算数据库的成本是一个非常简单的等式:软件成本硬件成本数据库成本。如果你选择了一个开源产品,软件成本可能会消失。虽然云计算已经从根本上改变了我们使用和部署软件的方式,但仍有太多人在使用这种过
使用element-ui 的上传组件upload完成自定义上传到天翼云oss云服务器
本文分享自天翼云开发者社区@《》,作者:我是小朋友首先配置天翼云,如下操作1、要求在使用OOS之前,首先需要在www.ctyun.cn注册一个账号(Account)。创建AccessKeyId和AccessSecretKey。AccessKeyId和Acc
PostgreSQL:启动与停止
本文分享自天翼云开发者社区@《》,作者:周平启动和停止PostgreSQL数据库服务器,通常使用pgctl。通常在我们的生产环境中,如果数据库主机发生意外停机或者由于计划内的硬件配置等操作停止了主机后,PostgreSQL服务也将会停止,需要手动重启。因此
HPC调度基础:slurm集群的部署
本文分享自天翼云开发者社区@《》,作者:才开始学技术的小白0.引言HPC(HighPerformanceComputing,以下简称HPC)是一个领域,试图在任何时间点和技术上对于相关技术、方法和应用等多种方面实现最大的计算能力;换而言之其目的就是求解一类
聊聊Docker镜像
本文分享自天翼云开发者社区@《​​​​​​​​​》,作者:AE86上山了。前言回顾前面:为什么需要Docker?Docker入门为什么可以这么简单?在上篇也同样留下一个问题:我们知道Tomcat运行起来需要Java的支持,那么我们在DockerHub拉取下
Dummynet简单部署
本文分享自天翼云开发者社区《Dummynet简单部署》,作者:凸凹部署流程^准备内核版本 ^参看系统内核版本unamer 我们需要将ipfw编译成内核模块,请确保ipfw用到的内核源码版本同你linux系统运行内核版本一致。
如何将物理机Windows系统迁移到VMware虚拟机?
本文分享自天翼云开发者社区@《》,作者:我是小朋友如何将物理机上的Windows系统迁移到VMware虚拟机?本文详细介绍如何使用DiskGenius免费版将物理电脑上的Windows操作系统转移至VMware虚拟机。关于迁移物理系统到VMware虚拟机V
Knative Autoscaler 自定义弹性伸缩
本文分享自天翼云开发者社区@《》,作者:我是小朋友背景如今各大云厂商都开始提供ServerlessKubernetes服务,简化集群管理,降低运维管理负担,让Kubernetes更加简单。那么问题来了,一个系统到底需要具备怎样的能力才能更好地支撑Serve
天翼云开发者社区
天翼云开发者社区
Lv1
天翼云是中国电信倾力打造的云服务品牌,致力于成为领先的云计算服务提供商。提供云主机、CDN、云电脑、大数据及AI等全线产品和场景化解决方案。
文章
586
粉丝
11
获赞
40