失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 灵魂拷问:到底要不要写单元测试 如何正确进行单元测试?

灵魂拷问:到底要不要写单元测试 如何正确进行单元测试?

时间:2019-05-13 09:51:45

相关推荐

灵魂拷问:到底要不要写单元测试 如何正确进行单元测试?

点击关注公众号,实用技术文章及时了解

来源:/new_com/article/details/116098959

为什么要写单元测试

一聊起测试用例,很多人第一反应就是,我们公司的测试会写测试用例的,我自己也会使用postman或者swagger之类的进行代码自测。那我们研发到底要不要写单元测试用例呢?

参考阿里巴巴开发手册,第8条规则(单元测试的基本目标:语句覆盖率达到 70%;核心模块的语句覆盖率和分支覆盖率都要达到 100%),大厂的要求就是必须喽。我个人感觉,写单元测试用例也是很有必要的,好处很多,例如:

保证代码质量!!!无论初级,中级,高级攻城狮开发工程的代码,且不说效率如何,功能是必要要保证是正确的;交付测试以后,bug锐减,联调飞快。

代码逻辑“文档化”!!!新人接手维护模块代码时,通过单元测试用例,以debug的方式就能熟悉业务代码。比起,看代码,研究表结构梳理代码结构,效率提升飞快。

易维护!!!新人接手维护代码模块时,提交自己的代码时,远行之前的单元测试达到回归测试,保证了新改动不会影响老业务。

快速定位bug!!!在联调期间,测试提出bug后,基于uat环境,编写出错的api测试用例。根据,测试提供的参数和token就可以以debug的方式跟踪问题的所在,如果是在微服务架构中,运行单元测试用例,不会注册本地服务到uat环境,还能过正常请求注册中心的服务。

到底如何写单元测试

Java开发springboot项目都是基于junit测试框架,比较MockitoJUnitRunnerSpringRunner的使用,MockitoJUnitRunner基于mockito,模拟业务条件,验证代码逻辑。SpringRunner是MockitoJUnitRunner子类,集成了Spring容器,可以在测试的根据配置加载Spring bean对象。

在Springboot开发中,结合@SpringBootTest注解,加载项目配置,进行单元测试。

基于MockitoJUnitRunner的方法测试

以springboot项目为例,一般,对单个的方法都是进行mock测试,在测试方法使用MockitoJUnitRunner,根据不同条件覆盖测试。使用@InjectMocks注解,可以让模拟的方法正常发起请求;@Mock注解可以模拟期望的条件。以删除菜单服务为例,源码如下:

@ServicepublicclassMenuManagerImplimplementsIMenuManager{/***删除菜单业务逻辑**/@Override@OptimisticRetry@Transactional(rollbackFor=Exception.class)publicbooleandelete(Longid){if(Objects.isNull(id)){returnfalse;}MenuexistingMenu=this.menuService.getById(id);if(Objects.isNull(existingMenu)){returnfalse;}if(!this.menuService.removeById(id)){thrownewOptimisticLockingFailureException("删除菜单失败!");}returntrue;}}/***删除菜单方法级单元测试用例**/@RunWith(MockitoJUnitRunner.class)publicclassMenuManagerImplTest{@InjectMocksprivateMenuManagerImplmenuManager;@MockprivateIMenuServicemenuService;@Testpublicvoiddelete(){Longid=null;booleanflag;//id为空flag=menuManager.delete(id);Assert.assertFalse(flag);//菜单返回为空id=1l;Mockito.when(this.menuService.getById(ArgumentMatchers.anyLong())).thenReturn(null);flag=menuManager.delete(id);Assert.assertFalse(flag);//修改成功MenumockMenu=newMenu();Mockito.when(this.menuService.getById(ArgumentMatchers.anyLong())).thenReturn(mockMenu);Mockito.when(this.menuService.removeById(ArgumentMatchers.anyLong())).thenReturn(true);flag=menuManager.delete(id);Assert.assertTrue(flag);}}

基于SpringRunner的Spring容器测试

在api开发过程中,会对单个api的调用链路进行验证,对第三方服务进行mock模拟,本服务的业务逻辑进行测试。

一般,会使用@SpringBootTest加载测试环境的Spring容器配置,使用MockMvc以http请求的方式进行测试。以修改新增菜单测试用例为例,如下:

/***成功新增菜单api*/@Api(tags="管理员菜单api")@RestControllerpublicclassAdminMenuController{@AutowiredprivateIMenuManagermenuManager;@PreAuthorize("hasAnyAuthority('menu:add','admin')")@ApiOperation(value="新增菜单")@PostMapping("/admin/menu/add")@VerifyLoginUser(type=IS_ADMIN,errorMsg=INVALID_ADMIN_TYPE)publicResponse<MenuVo>save(@Validated@RequestBodySaveMenuDtosaveMenuDto){returnResponse.success(menuManager.save(saveMenuDto));}}/***成功新增菜单单元测试用例*/@RunWith(SpringRunner.class)@SpringBootTest(classes=MallSystemApplication.class)@Slf4j@AutoConfigureMockMvcpublicclassAdminMenuControllerTestextendsBaseTest{/***成功新增菜单*/@Testpublicvoidsuccess2save()throwsException{SaveMenuDtosaveMenuDto=newSaveMenuDto();saveMenuDto.setName("重置密码");saveMenuDto.setParentId(1355339254819966978l);saveMenuDto.setOrderNum(4);saveMenuDto.setType(MenuType.button.getValue());saveMenuDto.setVisible(MenuVisible.show.getValue());saveMenuDto.setUrl("https:");saveMenuDto.setMethod(MenuMethod.put.getValue());saveMenuDto.setPerms("user:reset-pwd");//发起http请求MvcResultmvcResult=mockMvc.perform(MockMvcRequestBuilders.post("/admin/menu/add").contentType(MediaType.APPLICATION_JSON_UTF8_VALUE).content(JSON.toJSONString(saveMenuDto)).accept(MediaType.APPLICATION_JSON_UTF8_VALUE).header(GlobalConstant.AUTHORIZATION_HEADER,GlobalConstant.ADMIN_TOKEN)).andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();Response<MenuVo>response=JSON.parseObject(mvcResult.getResponse().getContentAsString(),menuVoTypeReference);//断言结果Assert.assertNotNull(response);MenuVomenuVo;Assert.assertNotNull(menuVo=response.getData());Assert.assertEquals(menuVo.getName(),saveMenuDto.getName());Assert.assertEquals(menuVo.getOrderNum(),saveMenuDto.getOrderNum());Assert.assertEquals(menuVo.getType(),saveMenuDto.getType());Assert.assertEquals(menuVo.getVisible(),saveMenuDto.getVisible());Assert.assertEquals(menuVo.getStatus(),MenuStatus.normal.getValue());Assert.assertEquals(menuVo.getUrl(),saveMenuDto.getUrl());Assert.assertEquals(menuVo.getPerms(),saveMenuDto.getPerms());Assert.assertEquals(menuVo.getMethod(),saveMenuDto.getMethod());}}

具体编写单元测试用例规则参考测试用例的编写。简单说,一般api的单元测试用例,编写两类,如下:

业务参数的校验,和义务异常的校验。例如,名称是否为空,电话号码是否正确,用户未登陆则抛出未登陆异常。

各类业务场景的真实测试用例,例如,编写成功添加顶级菜单的测试用例,已经编写成功添加子级菜单的测试用例。

注意事项

配置覆盖

此外,如上基于mockmvc的编写的测试用例,由于加载了Spring的配置,会对项目发起真实的调用。如果,环境的配置为线上配置,容易出现安全问题。

一般出于安全考虑,很多公司会对真实环境的修改操作做事务回滚操作,甚至根本就不会进行真实环境的调用,使用模拟环境替换,例如数据库的操作可以使用h2内存数据库进行替换。

这时,可以在src/test/resources目录下,添加与src/main/resources目录下,相同的文件进行配置覆盖。src/test/main目录下的代码,会首先加载src/test/resources目录下的配置,如果没有则在加载src/main/resources目录的配置。常用场景如下:

在单元测试环境使用使用内存数据库。

ginkens代码集成运行测试用例时,不希望在集成环境中输出日志文件信息,并且以debug级别输出日志。

以日志文件配置覆盖为例,在src/main/resources目录下配置日志有文件和控制台输出,如图:

main/resource目录下的logback-spring.xml,内容如下:

<?xmlversion="1.0"encoding="UTF-8"?><configurationscan="true"><contextName>mall-system</contextName><!--控制台日志输出配置--><appendername="STDOUT"class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>[%d{yyyy-MM-ddHH:mm:ss.SSS}][%level][%thread][%contextName][%logger{80}:%L]%msg%n</pattern><charset>UTF-8</charset></encoder><filterclass="ch.qos.logback.classic.filter.ThresholdFilter"><level>DEBUG</level></filter></appender><!--日志文件输出配置--><appendername="FILE"class="ch.qos.logback.core.rolling.RollingFileAppender"><file>log/info.log</file><rollingPolicyclass="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><fileNamePattern>log/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern><maxFileSize>50MB</maxFileSize><maxHistory>50</maxHistory><totalSizeCap>10GB</totalSizeCap></rollingPolicy><encoder><pattern>[%d{yyyy-MM-ddHH:mm:ss.SSS}][%level][%contextName][%logger{80}:%L]%msg%n</pattern></encoder></appender><!--设置INFO级别输出日志--><rootlevel="INFO"><appender-refref="STDOUT"/><appender-refref="FILE"/></root></configuration>

src/test//resource目录下的新增logback-spring.xml,去掉日志文件输出的配置,设置日志输出级别为DEBUG;如果运行测试用例,则加载该配置不会进行日志文件的输出,并且打印DEBUG级别日志。如图:

<?xmlversion="1.0"encoding="UTF-8"?><configurationscan="true"><contextName>mall-system</contextName><!--控制台日志输出--><appendername="STDOUT"class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>[%d{yyyy-MM-ddHH:mm:ss.SSS}][%level][%thread][%contextName][%logger{80}:%L]%msg%n</pattern><charset>UTF-8</charset></encoder><filterclass="ch.qos.logback.classic.filter.ThresholdFilter"><level>DEBUG</level></filter></appender><!--DEBUG级别日志输出--><rootlevel="DEBUG"><appender-refref="STDOUT"/></root></configuration>

指定环境

一般开发过程中,我们研发只会操作开发环境,也是为了避免数据安全问题,可以在单元测试用例中指定运行的环境配置。在测试类加上@ActiveProfiles("dev"),指定获取dev环境的配置。示例,

/***获取dev环境配置*/@RunWith(SpringRunner.class)@SpringBootTest(classes=MallSystemApplication.class)@Slf4j@AutoConfigureMockMvc@ActiveProfiles("dev")publicclassAdminMenuControllerTestextendsBaseTest{}

在联调测试中,对于出错的api,可以编写对应的单元测试用例,使用@ActiveProfiles("uat")指定到测试环境,就可以根据测试提供的参数快速定位问题。示例:

/***新增菜单api联调*/@RunWith(SpringRunner.class)@SpringBootTest(classes=MallSystemApplication.class)@Slf4j@AutoConfigureMockMvc@ActiveProfiles("uat")publicclassAdminMenuControllerTestextendsBaseTest{/***成功新增菜单*/@Testpublicvoidsuccess2save()throwsException{Stringtoken="BearereyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjhjMjhlZWEzLTA5MWEtNDA1OS1iMzliLTRjOGMyNGY4ZjEzMiJ9.xK9srWjeGaq4NXt4BzG2MQ_yN9IaYtPVjKj5MoSS4bX9Ytf1XJNe_NSupR0IItkB48G6mXVZwj5CIwWIYzvsEA";StringparamJson="{"name":"mayuan","parentId":"1","orderNum":"1","type":"1","visible":true,"url":"https:","method":2,"perms":"user:reset-pwd"}";//发起http请求MvcResultmvcResult=mockMvc.perform(MockMvcRequestBuilders.post("/admin/menu/add").contentType(MediaType.APPLICATION_JSON_UTF8_VALUE).content(paramJson).accept(MediaType.APPLICATION_JSON_UTF8_VALUE).header(GlobalConstant.AUTHORIZATION_HEADER,token)).andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();}}

推荐:

主流Java进阶技术(学习资料分享)

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

如果觉得《灵魂拷问:到底要不要写单元测试 如何正确进行单元测试?》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。