spring-boot单元测试
一、为什么要写单元测试
很多程序员有两件事情不愿意做:
- 写注释。
- 写单元测试。
但是在看代码时又会希望有清晰明了的注释,重构代码时能有一套随时可以跑起来的单元测试。
最近在迁移一个项目,从sqlserver迁移到mysql,功能不变,部分语法有些不一样,人工校验是一件痛苦的事情,但是全依靠测试又怕有遗漏,于是研究了下单元测试,这就是写这篇博文的动机。单元测试能够大较大范围检测我们代码调整后修改后的逻辑问题,达到一次编写多次使用的目的。
二、怎么写单元测试
写单元测试有很多框架,有针对Service的、有针对Controller的等等,这里主要使用MockMvc针对Rest风格的Controller进行单元测试。同时,为了避免造成脏数据污染测试环境,对数据库的增删改进行了事物回滚配置,达到既完成了测试,又不需要手动清理数据的目的。
使用MockMvc单元测试,可以模拟真实的Rest风格的接口调用情况,全流程覆盖测试。
单元测试运行顺序:(摘自:http://blog.csdn.net/u011794238/article/details/50639187)

怎么写单元测试要看有什么要的业务需求,总结有以下几大写法:
1、方法限时测试
- 可用来模拟接口最小延时测试
- 可用来模拟死循环测试
@Test(timeout = ) // 单位毫秒 10秒时间限制
public void testDemo(){ }
2、异常测试(必须要抛出指定异常测试)
期望某种异常,如果没有指定的异常,会测试不通过。
@Test(expected = ArithmeticException.class)
public void testDemo(){ }
3、忽略测试方法
@Ignore注解,此时即使某方法包含@Test注解,该方法也不会作为测试方法执行,@Ignore还可以注解在类上,当一个类存在@Ignore注解时,该类所有的方法都不会被认为是测试方法。
@Ignore("method not yet implemented")
@Test
public void testDemo(){
}
如果你已经把该方法的测试用例写完,但该方法尚未完成,那么测试的时候一定是“失败”。这种失败和真正的失败是有区别的,因此JUnit提供了一种方法来区别他们,那就是在这种测试函数的前面加上@Ignore标注,这个标注的含义就是“某些方法尚未完成,暂不参与此次测试”。这样的话测试结果就会提示你有几个测试被忽略,而不是失败。一旦你完成了相应函数,只需要把@Ignore标注删去,就可以进行正常的测试。
4、Fixture
@Before:在任何一个测试执行之前必须执行的代码,我们用@Before标注它。(在每个测试方法执行前执行)
@After:在任何测试执行之后需要进行的收尾工作,使用@After来标注。(在每个测试方法执行结束后执行)
@BeforeClass:只在测试用例初始化时执行 @BeforeClass 方法。
@AfterClass:当所有测试执行完毕之后,执行 @AfterClass 进行收尾工作。
每个测试类只能有一个方法被标注为 @BeforeClass 或 @AfterClass ,并且该方法必须是 Public 和 Static 的。
@Before
public void testDemo(){
System.out.println("before test demo method ……");
}
@Before
public void testDemo2(){
System.out.println("before test demo2 method ……");
}
两个Before方法都会执行,但不保证顺序,没必要注解两个Before方法。
4、Runner( 运行器 )
当你把测试代码提交给 JUnit 框架后,框架通过Runner来运行测试代码。在 JUnit 中有很多个 Runner ,他们负责调用你的测试代码,每一个 Runner 都有各自的特殊功能,你要根据需要选择不同的 Runner 来运行你的测试代码。
比如:
参数化测试Runner @RunWith(Parameterized.class)
打包测试Runner @RunWith(Suite.class)
5、 参数化测试
JUnit4 提出了“参数化测试”的概念,只写一个测试函数,把这若干种情况作为参数传递进去,一次性的完成测试。
6、打包测试
如果将所有的测试方法写在同一个测试类中,不仅类代码太多,找某个功能点的测试方法也不太方便,所有通常我们按Controller写对应的测试类,这样一来,要跑完所有的测试方法就必须一个个测试类来跑测试。
Junit提供了一个打包运行所有测试类的方法。示例:
package com.generator.demo.controller; import org.junit.runner.RunWith;
import org.junit.runners.Suite; @RunWith(Suite. class )
@Suite.SuiteClasses( {
OrderControllerTest.class,
PayControllerTest.class
} )
public class AllTestRunner {
}
示例可以运行 OrderControllerTest 和 PayControllerTest 里的所有测试方法。以此类推,将所有的测试类写在这里,只需要运行 AllTestRunner 就可以达到运行所有测试类的目的。
7、事物自动回滚
在测试类上加注解,可以对数据库增删改操作回滚,测试用例可以反复使用。
@Rollback 默认是回滚
@RunWith(SpringRunner.class)
@SpringBootTest
@Rollback
@Transactional(value = "testpackageTransactionManager")
public class OrderControllerTest { }
三、单元测试示例
1、打包测试示例:
单元测试的这几个注解都是可继承的
- @RunWith
- @SpringBootTest
- @Rollback
- @Transactional
申明所有测试类继承的父类BaseTest:
@RunWith(SpringRunner.class)
@SpringBootTest
@Rollback
@Transactional(value = "testpackageTransactionManager")
public class BaseTest {
}
ControlerATest与ControllerBTest:
public class ControllerATest extend BaseTest {
@Test
public void testAMethodA(){
System.out.println("com.generator.demo.controller.ControllerATest.testAMethodA");
}
@Test
public void testAMethodB(){
System.out.println("com.generator.demo.controller.ControllerATest.testAMethodB");
}
}
RunAllTest:
@RunWith(Suite.class)
@Suite.SuiteClasses({
ControllerATest.class,
ControllerBTest.class
})
public class RunAllTest { }
类图示例:

只需要运行RunAllTest,输出如下:
com.generator.demo.controller.ControllerATest.testAMethodA
com.generator.demo.controller.ControllerATest.testAMethodB
com.generator.demo.controller.ControllerBTest.testBMethodA
com.generator.demo.controller.ControllerBTest.testBMethodBProcess finished with exit code 0
2、Rest风格测试示例
@RunWith(SpringRunner.class)
@SpringBootTest
@Rollback
@Transactional(value = "testpackageTransactionManager")
public class OrderControllerTest {
/**
* mockMvc: 这个对象是Controller单元测试的关键,它的初始化也是在setup方法里面。
mockMvc.perform: 发起一个http请求。
post(url): 表示一个post请求,url对应的是Controller中被测方法的Rest url。
param(key, value): 表示一个request parameter,方法参数是key和value。
andDo(print()): 表示打印出request和response的详细信息,便于调试。
andExpect(status().isOk()): 表示期望返回的Response Status是200。
andExpect(content().string(is(expectstring)): 表示期望返回的Response Body内容是期望的字符串。
*/
private MockMvc mockMvc; //用于声明一个ApplicationContext集成测试加载WebApplicationContext。作用是模拟ServletContext
@Autowired
private WebApplicationContext wac; @Before
public void setup() {
//MockMvcBuilders使用构建MockMvc对象 (项目拦截器有效)
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
} /**Post方式*/
@Test(timeout = 10000)
public void getOrderById() throws Exception {
GetOrderByIdRequest request = new GetOrderByIdRequest();
request.setId(1); MockHttpServletRequestBuilder content = MockMvcRequestBuilders.post("/order/getOrderById")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(JsonUtil.convertToJson(request)); ResultActions resultActions = mockMvc.perform(content)
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk());
//.andExpect(MockMvcResultMatchers.content().string("365")); //测试接口返回内容
MvcResult mvcResult = resultActions.andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
String contentAsString = response.getContentAsString(); Assert.assertTrue("请求成功!", response.getStatus() == 200);
System.out.println(contentAsString);
} @Test
public void getOrderById() throws Exception {
MockHttpServletRequestBuilder content = MockMvcRequestBuilders.get("/order/getOrderById")
.contentType(MediaType.APPLICATION_FORM_URLENCODED).param("id", "1"); ResultActions resultActions = mockMvc.perform(content)
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("id").value(1));
//.andExpect(MockMvcResultMatchers.content().string("365")); //测试接口返回内容
MvcResult mvcResult = resultActions.andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
String contentAsString = response.getContentAsString(); Assert.assertTrue("请求成功!", response.getStatus() == 200);
System.out.println(contentAsString);
}
}
参考资料:
参数化示例 http://blog.csdn.net/u012777182/article/details/38821307
初级测试 http://blog.csdn.net/andycpp/article/details/1327147
中级测试 http://blog.csdn.net/andycpp/article/details/1327346
高级测试 http://blog.csdn.net/andycpp/article/details/1329218
spring-boot单元测试的更多相关文章
- Spring Boot单元测试(Mock)
Spring Boot单元测试(Mock) Java个人学习心得 2017-08-12 16:07 Mock 单元测试的重要性就不多说了,我这边的工程一般都是Spring Boot+Mybatis(详 ...
- Spring Boot 单元测试示例
Spring 框架提供了一个专门的测试模块(spring-test),用于应用程序的单元测试. 在 Spring Boot 中,你可以通过spring-boot-starter-test启动器快速开启 ...
- Spring Boot 单元测试详解+实战教程
Spring Boot 的测试类库 Spring Boot 提供了许多实用工具和注解来帮助测试应用程序,主要包括以下两个模块. spring-boot-test:支持测试的核心内容. spring-b ...
- spring boot 单元测试,如何使用profile
一.问题概述 spring boot项目.单元测试的时候,我发现,总是会使用application.properties的内容,而该文件里,一般是我的开发时候的配置. 比如上图中,dev是开发配置,p ...
- Java程序员的日常—— Spring Boot单元测试
关于Spring boot 之前没有用Spring的时候是用的MockMvc,做接口层的测试,原理上就是加载applicationContext.xml文件,然后模拟启动各种mybatis\连接池等等 ...
- 48. spring boot单元测试restfull API【从零开始学Spring Boot】
回顾并详细说明一下在在之前章节中的中使用的@Controller.@RestController.@RequestMapping注解.如果您对Spring MVC不熟悉并且还没有尝试过快速入门案例,建 ...
- IDEA + Spring boot 单元测试
1. 创建测试类 打开IDEA,在任意类名,任意接口名上,按ctrl+shift+t选择Create New Test image 然后根据提示操作(默认即可),点击确认,就在项目的/test/jav ...
- spring boot单元测试(转)
Junit这种老技术,现在又拿出来说,不为别的,某种程度上来说,更是为了要说明它在项目中的重要性.凭本人的感觉和经验来说,在项目中完全按标准都写Junit用例覆盖大部分业务代码的,应该不会超过一半. ...
- spring boot 单元测试 --- 在测试类使用 javabean注解操作接口
1.依赖包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>s ...
- spring boot 单元测试
Java新手 纯记录 @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = OutDemoApplication.clas ...
随机推荐
- 日期选择时两个日期之间的动态控制--My97datepicker日期选择控件
实现效果:如果先选离店日期,再选入住日期的话,入住日期大于离店日期则离店日期+1天否则离店日期不变,先选入店再选离店离店,离店只能选之后的日期,且两个日期之间最多间隔88天 <div class ...
- AI画圆角矩形
如何画圆角矩形:设置矩形圆角大小 第一种方法:点击圆角矩形在画布上点一下; [caption id="attachment_878" align="alignnone&q ...
- react-native 学习(一)
本包子很久没更新过博客啊... 学习react-native 可以从官网上去学习.但是 目前我看到的中文网和英文网他们初始构建的项目的命令行是不同的. 在中文网上,构建项目的 react-native ...
- c# winfrom实时获取斗鱼房间弹幕
效果图如下: 通过webBrowser获取,时钟控件刷新弹幕,正则匹配数据,用第二个webBrowser显示弹幕内容.老话,并没完善.请自行完善.有个dll是用来屏蔽webBrowser的声音的,可能 ...
- combined with the Referer header, to potentially build an exhaustive data set of user profiles and browsing habits Client Identification
w https://www.zhihu.com/question/35307626 w 0-客户端(附加用户信息)首次请求服务端--->服务端生成session(有唯一性).session_id ...
- [LeetCode] 7.Reverse Integer - Swift
Reverse digits of an integer. Example1: x = , return Example2: x = -, return - 题目意思:对一个整型进行反转 实现代码: ...
- 并发编程 - 进程 - 1.队列的使用/2.生产者消费者模型/3.JoinableQueue
1.队列的使用: 队列引用的前提: 多个进程对同一块共享数据的修改:要从硬盘读文件,慢,还要考虑上锁: 所以就出现了 队列 和 管道 都在内存中(快): 队列 = 管道 + 上锁 用队列的目的: 进程 ...
- sqlserver表结构查询语句
SELECT syscolumns.name,systypes.name,syscolumns.isnullable,syscolumns.length FROM syscolumns, systyp ...
- 013-HQL中级3-Hive四种数据导入方式介绍
Hive的几种常见的数据导入方式这里介绍四种:(1).从本地文件系统中导入数据到Hive表:(2).从HDFS上导入数据到Hive表:(3).从别的表中查询出相应的数据并导入到Hive表中:(4).在 ...
- void指针意义、Const、volatile、#define、typedef、接续符
1.C语言规定只有相同类型的指针才可以相互赋值. Void*指针作为左值用于接收任意类型的指针, void*指针作为右值赋给其他指针时需要强制类型转换. 2.在C语言中Const修饰的变量是只读的,本 ...