springboot使用单元测试实战
前言
springboot提供了spirng-boot-starter-test以供开发者使用单元测试,在引入spring-boot-starter-test依赖后:
org.springframework.boot spring-boot-starter-test test
其中包含以下几个库:
- Junit——常用的单元测试库
- SpringTest&SpringBootTest——对Spring应用的集成测试支持
- AssertJ——一个断言库
- Hamcrest——一个匹配对象的库
- Mockito——一个Java模拟框架
- JSONassert——一个针对JSON的断言库
- JsonPath——用于JSON的XPath
下面我们将从Service层和Controller层的角度来简单介绍下单元测试
Service单元测试
在SpringBoot2.0中,创建一个Service的单元测试,代码如下:
@RunWith(SpringRunner.class) @SpringBootTest publicclassUserServiceImplTest{ @Autowired privateUserServiceuserService; @Test publicvoidinsertUser(){ Useruser=newUser(); user.setUsername("lining"); user.setPassword("123456"); userService.insertUser(user); } }
上面的测试非常简单,主要需要注意两个注解:@RunWith和@SpringBootTest
- @RunWith:该注解标签是Junit提供的,用来说明此测试类的运行者,这里用了SpringRunner,它实际上继承了SpringJUnit4ClassRunner类,而SpringJUnit4ClassRunner这个类是一个针对Junit运行环境的自定义扩展,用来标准化在Springboot环境下Junit4.x的测试用例
- @SpringBootTest为springApplication创建上下文并支持SpringBoot特性
使用@SpringBootTest的webEnvironment属性定义运行环境:
- Mock(默认):加载WebApplicationContext并提供模拟的web环境Servlet环境,使用此批注时,不会启动嵌入式服务器
- RANDOM_PORT:加载WebServerApplicationContext并提供真实的web环境,嵌入式服务器,监听端口是随机的
- DEFINED_PORT:加载WebServerApplicationContext并提供真实的Web环境,嵌入式服务器启动并监听定义的端口(来自application.properties或默认端口8080)
- NONE:使用SpringApplication加载ApplicationContext但不提供任何Web环境
Controller的单元测试
首先创建一个Controller,代码如下:
@RestController publicclassUserController{ @Autowired privateUserServiceuserService; @PostMapping("/user") publicStringuserMapping(@RequestBodyUseruser){ userService.insertUser(user); return"ok"; } }
然后创建Controller的单元测试,一般有两种创建方法。
第一种使用模拟环境进行测试
默认情况下,@SpringBootTest不会启动服务器,如果需针对此模拟环境测试Web端点,可以如下配置MockMvc:
@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc publicclassUserControllerTest{ @Autowired privateMockMvcmockMvc; @Test publicvoiduserMapping()throwsException{ Stringcontent="{\"username\":\"pj_mike\",\"password\":\"123456\"}"; mockMvc.perform(MockMvcRequestBuilders.request(HttpMethod.POST,"/user") .contentType("application/json").content(content)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().string("ok")); } }
这里有一个@AutoConfigureMockMvc注解,该注解表示启动测试的时候自动注入MockMvc,而这个MockMvc有以下几个基本的方法:
- perform:执行一个RequestBuilder请求,会自动执行SpringMVC的流程并映射到相应的控制器执行处理。
- andExpect:添加RequsetMatcher验证规则,验证控制器执行完成后结果是否正确
- andDo:添加ResultHandler结果处理器,比如调试时打印结果到控制台
- andReturn:最后返回相应的MvcResult,然后进行自定义验证/进行下一步的异步处理
这里有一个小技巧,一般来说对于一个controller中往往有不止一个Request请求需要测试,敲打MockMvcRequestBuilders与MockMvcResultMatchers会显得比较繁琐,有一个简便的方法就是将这两个类的方法使用importstatic静态导入,然后就可以直接使用两个类的静态方法了。然后代码就变成如下所示:
... importstaticorg.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; importstaticorg.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc publicclassUserControllerTest{ @Autowired privateMockMvcmockMvc; @Test publicvoiduserMapping()throwsException{ Stringcontent="{\"username\":\"pj_mike\",\"password\":\"123456\"}"; mockMvc.perform(request(HttpMethod.POST,"/user") .contentType("application/json").content(content)) .andExpect(status().isOk()) .andExpect(content().string("ok")); } }
另外,如果是只想关注Web层而不是启动完整的ApplicationContext,可以考虑使用@WebMvcTest注解,该注解不能与@SpringBootTest搭配使用,而且它只关注Web层面,至于涉及到数据层的时候,需要引入相关依赖,关于这个注解更多的介绍请参阅官方文档:https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-mvc-tests
使用MockMvcBuilder构建MockMvc对象
除了上面用@AutoConfigureMockMvc注解直接自动注入MockMvc的方式,我们还可以利用MockMvcBuilder来构建MockMvc对象,示例代码如下:
@RunWith(SpringRunner.class) @SpringBootTest publicclassUserControllerTest4{ @Autowired privateWebApplicationContextweb; privateMockMvcmockMvc; @Before publicvoidsetupMockMvc(){ mockMvc=MockMvcBuilders.webAppContextSetup(web).build(); } @Test publicvoiduserMapping()throwsException{ Stringcontent="{\"username\":\"pj_m\",\"password\":\"123456\"}"; mockMvc.perform(request(HttpMethod.POST,"/user") .contentType("application/json").content(content)) .andExpect(status().isOk()) .andExpect(content().string("ok")); } }
第二种使用真实Web环境进行测试
在@SpringBootTest注解中设置属性webEnvironment=WebEnvironment.RANDOM_PORT,每次运行的时候会随机选择一个可用端口。我们也可以还使用@LoalServerPort注解用于本地端口号。下面是测试代码:
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT) publicclassUserControllerTest3{ @Autowired privateTestRestTemplatetestRestTemplate; @Test publicvoiduserMapping()throwsException{ Useruser=newUser(); user.setUsername("pj_pj"); user.setPassword("123456"); ResponseEntityresponseEntity=testRestTemplate.postForEntity("/user",user,String.class); System.out.println("Result:"+responseEntity.getBody()); System.out.println("状态码:"+responseEntity.getStatusCodeValue()); } }
上面的代码中有一个关键的类——TestRestTemplate,TestRestTemplate是Spring的RestTemplate的一种替代品,可用于集成测试,更RestTemplate的使用功能方法类似,一般用于真实web环境测试中,关于该类更加详细的用法参考官方文档:https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#boot-features-test-scope-dependencies
单元测试回滚
单元测试的时候,如果不想造成垃圾数据,可以开启事务功能,在方法或类头部添加@Transactional注解即可,在官方文档中对此也有说明:
Ifyourtestis@Transactional,itrollsbackthetransactionattheendofeachtestmethodbydefault.However,asusingthisarrangementwitheitherRANDOM_PORTorDEFINED_PORTimplicitlyprovidesarealservletenvironment,theHTTPclientandserverruninseparatethreadsand,thus,inseparatetransactions.Anytransactioninitiatedontheserverdoesnotrollbackinthiscase
解读一下,在单元测试中使用@Transactional注解,默认情况下在测试方法的末尾会回滚事务。然而有一些特殊情况需要注意,当我们使用RANDOM_PORT或DEFINED_PORT这种安排隐式提供了一个真正的Servlet环境,所以HTTP客户端和服务器将在不同的线程中运行,从而分离事务,这种情况下,在服务器上启动的任何事务都不会回滚。
当然如果你想关闭回滚,只要加上@Rollback(false)注解即可,@Rollback表示事务执行完回滚,支持传入一个value,默认true即回滚,false不回滚。
还有一种情况需要注意,就是如果你使用的数据库是MySQL,有时候会发现加了注解@Transactionl也不会回滚,那么你就要查看一下你的默认引擎是不是InnoDB,如果不是就要改成InnoDB。
MyISAM与InnoDB是mysql目前比较常用的两个数据库引擎,MyISAM与InnoDB的主要的不同点在于性能和事务控制上,这里简单介绍下两者的区别与转换方法:
- MyISAM:MyISAM是MySQL5.5之前版本默认的数据库存储引擎,MyISAM提供高速存储和检索,以及全文搜索能力,适合数据仓库等查询频繁的应用,但不支持事务和外键,不能在表损坏后恢复数据
- InnoDB:InnoDB是MySQL5.5版本的默认数据库存储引擎,InnoDB具有提交,回滚和崩溃恢复能力的事务安全,支持事务和外键,比起MyISAM,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。
如果你的数据表是MyISAM引擎,由于它不支持事务,在单元测试中添加事务注解,测试方法也是不会回滚的。
修改默认引擎
查看MySQL当前默认的存储引擎
mysql>showvariableslike'%storage_engine%';
看具体的表user表用了什么引擎(engine后面的就表示当前表的存储引擎)
mysql>showcreatetableuser;
将user表修为InnoDB存储引擎
mysql>ALTERTABLEuserENGINE=INNODB;
注意
这里还有一点需要注意的地方,当我们使用SpringDataJPA时,如果没有指定MySQL建表时的存储引擎,默认情况下会使用MySQL的MyISAM,这也是一个坑点,这种情况下,你在单元测试使用@Transactional注解,回滚不会起作用。
解决方法是将hibernate.dialect属性配置成hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect,指定MySQL建表的时候使用InnoDB引擎,示例配置文件如下:
spring: jpa: #数据库类型 database:mysql #输出日志 show-sql:true properties: hibernate: #JPA配置 hbm2ddl.auto:update #mysql存储类型配置 dialect:org.hibernate.dialect.MySQL5InnoDBDialect
小结
上面简单总结了springboot下如何使用单元测试,关于单元测试更加详细的介绍请参阅官方文档:https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#boot-features-testing
参考资料&鸣谢
springboot官方文档https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#boot-features-testing
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。