SpringBoot系列: 单元测试2
之前发了SpringBoot 单元测试的博客, https://www.cnblogs.com/harrychinese/p/springboot_unittesting.html , 内容较少, 现在补齐SpringBoot单元测试的主要知识点.
测试有很多种, 有单元测试 、集成测试 、冒烟测试 、回归测试 、端到端测试 、功能测试。 它们作用不同, 但又有所重叠.
1. 单元测试: [责任人: 开发人员]针对 class 级别的测试, 针对一个类, 写一个测试类. 在项目中, class 之间依赖调用是很常见的事情, 如果要测试的 classA, 又调用了classB, 这时候就没有遵守 "在一个class范围内测试", 自然不算单元测试, 应归为集成测试了. 不过这时候, 我们可以使用 mock 技术模拟被依赖的 classB, 这样整个测试又聚焦在classA 自身的功能, 所以又变回为单元测试.
2. 集成测试: [责任人: 开发人员]是单元测试在粒度上更进一步, 测试不同class之间的组合功能.
3. 冒烟测试: 可以理解为预测试, 一旦预测试失败(发现重大bug), 就直接停止测试, 打回给开发人员.
4. 回归测试: 重跑原有测试用例.
=====================
spring-boot-starter-test 依赖包括:
=====================
1. JUnit, 测试框架
2. Spring Test & Spring Boot Test,
3. Mockito, Mock框架, 可以模拟任何 Spring 管理的 bean. 在单元测试过程中, 使用 @MockBean 注解可以将一个业务Service 的模拟器注入到我们要测试的SpringBoot程序中, 而不是真正的业务Service实现类.
4. JSONassert,可以对json内容进行断言.
5. JsonPath, 提供类类似于 XPath 的 json 路径访问形式.
=====================
JUnit 方法级别的注解
=====================
JUnit 4.x 全面引入了注解方式进行单元测试, 测试相关的方法都必须是 public .
@BeforeClass -- 用来修饰static方法, 在测试类加载时执行.
@AfterClass -- 用来修饰static方法, 在测试类结束时执行.
@Before -- 用来修饰实例方法, 在每个测试方法之前执行, 比如用来初始化一个 MockMvc 对象.
@After -- 用来修饰实例方法, 在每个测试方法之后执行.
@Test -- 用来修饰实例方法, 是测试用例方法.
@Transactional --和@Test一起搭配使用, 作用是自动回滚事务, 防止单元测试污染测试数据库.
@Rollback(false) --和@Transactional/@Test一起搭配使用, 用来提交事务.
=====================
JUnit 类级别注解
=====================
1. @RunWith -- 对于SpringBoot程序, 使用 @RunWith(SpringRunner.class)
2. @SuiteClasses -- 在IDE中一次只能执行一个测试类, 对于一个大型项目, 肯定不止一个测试类, 如何一次执行多个测试类呢? 答案就是使用@SuiteClasses. 该注解能将多个测试class归并到一个新的测试class, 新的测试类往往是一个空类, 需要加上 @RunWith(Suite.class), 代码如下:
@RunWith(Suite.class)
@SuiteClasses({Test1.class, Test2.class})
public class TestSuiteMain{
// 空类, 但它会执行 TestSuite1 和 TestSuite2 的所有测试方法
}
=====================
JUnit 断言
=====================
assertEquals() --是否相等
assertSame() --判断是否是同一个对象
assertTrue()
assertFalse()
assertNotNull()
assertArrayEquals()
assertThat() -- JUnit4.4 新增一个全能的断言
=====================
Spring test 提供的类级别注解
=====================
Spring 提供了一系列的测试方式注解, 我们可以按需选择它们, @SpringBootTest 主要用于集成测试, 其他注解用于单元测试.
(1). @SpringBootTest, 该注解负责扫描配置来构建测试用的Spring上下文环境. 将在启动单元测试之前, 首先按照包名逐级向上找 SpringBoot 业务系统的入口, 并启动该SpringBoot 程序. 所以启动速度较慢, 用于集成测试.
如果我们需要要注入 WebApplicationContext 和 MockMvc bean, 需要在类上再加一个 @AutoConfigureMockMvc 注解. 完整示例见下面的 MyControllerTests 代码.
(2). @WebMvcTest, 该注解仅仅扫描并初始化与 Controller 相关的bean, 但一般的@Component并不会被扫描, 另外也不启动一个 http server, 所以能加快测试进程. @WebMvcTest 还可以传入 Controller 类清单参数, 进一步缩小容器中bean的数量.
@WebMvcTest 还会自动实例化一个 WebApplicationContext 和 MockMvc bean, 供我们在测试用例中使用.
(3). @RestClientTest 和 @WebMvcTest 类似, 用来测试基于Rest API的Service 类, 该注解会自动实例化一个MockRestServiceServer bean用来模拟远程的 Restful 服务.
@RunWith(SpringRunner.class)
@RestClientTest(RemoteVehicleDetailsService.class)
public class ExampleRestClientTest { @Autowired
private RemoteVehicleDetailsService service; @Autowired
private MockRestServiceServer server; @Test
public void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails()
throws Exception {
this.server.expect(requestTo("/greet/details"))
.andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
String greeting = this.service.callRestService();
assertThat(greeting).isEqualTo("hello");
}
}
(4). 除了上面几个类型, 还有 @MybatisTest 、 @JsonTest 、 @DataRedisTest 等等.
=====================
MockMvc 基本使用
=====================
MockMvc bean, 使用该对象的 perform() 可以发出模拟web请求, 并完成期望检查, 当然期望检查还可以使用JUnit 的 Assert. 虽然 MockMvc 是一个模拟的Http调用, 但它确实真实调用了视图函数, 并以 http 协议封装了结果, 所以可以用来做单元测试.
@RestController
@RequestMapping("/")
class MyController { @GetMapping("/")
public String index() throws SQLException {
return "index";
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MyControllerTests { @Autowired
private MockMvc mvc; /**
* 期望调用成功, 并打印完整结果.
*
* @throws Exception
*/
@Test
public void indexPrint() throws Exception { //@formatter:off
mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
//@formatter:on
} /**
* 期望调用成功, 并验证该视图函数的返回内容是否为字符串 index
*
* @throws Exception
*/
@Test
public void index() throws Exception {
String expected = "index";
//@formatter:off
mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string(expected));
//@formatter:on
}
}
=====================
Mockito基本使用
=====================
顶层class经常会依赖一些底层class, 要对顶层class做单元测试, 就需要使用 mock 技术来代替底层class.
1. @MockBean 注解, 该 Mockito 注解可注入顶层对象, 它和 @Autowired 用法含义差不多, 但并不会注入真实的底层实现类.
2. 使用 Mockito.when() 来模拟底层类的行为:
Mockito.when(methodCall).thenReturn(expected) 使用穷举法来模拟, 这个方式简单但最常用, 我们写测试用例基本上也是按照case by case 设计有限的几个测试用例.
Mockito.when(methodCall).thenAnswer(匿名对象) 通过when()传入的参数动态模拟, 该方式很强大, 但并不常用, 因为我们在测试用例中, 没有必要再和被模拟对象一样实现一套业务逻辑.
下面是被测试的代码, MyServiceConsumer 依赖一个 MyService 接口.
interface MyService {
public String appendA(String source);
} @Service
class DefaultMyService implements MyService { @Override
public String appendA(String source) {
System.out.println("MyServiceConsumer.appendA() called");
return source + "B";
}
} @Component
class MyServiceConsumer {
@Autowired
MyService myService; public String getServiceName(String source) {
return myService.appendA(source);
}
}
下面是MyServiceConsumer 的测试代码, 我们使用mockito 模拟了一个依赖 MyService 对象.
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyServiceConsumerTests {
@MockBean
MyService myService; @Autowired
MyServiceConsumer myServiceConsumer; /*
* 使用 MockBean 来模拟 MyService 的 appendA() 行为
*/
@Before
public void Init() {
// 方式1: 使用穷举法模拟, 具体是通过 thenReturn()方法
String source = "MyService1";
Mockito.when(myService.appendA(source))
.thenReturn(source + "A"); // 方式2:使用动态参数形式模拟, 具体是通过 thenAnswer()方法
Mockito.when(myService.appendA(Mockito.anyString()))
.thenAnswer(new Answer<String>() { @Override
public String answer(InvocationOnMock invocation) throws Throwable {
String arg = (String) invocation.getArgument(0);
return arg + "A";
}
});
} @Test
public void getServiceName1() {
String source = "MyService1";
String expected = "MyService1A";
String actual = myServiceConsumer.getServiceName(source);
Assert.assertEquals("错误: getServiceName() 不符合预期", expected, actual);
} @Test
public void getServiceName2() {
String source = "Other";
String expected = "OtherA";
String actual = myServiceConsumer.getServiceName(source);
Assert.assertEquals("错误: getServiceName() 不符合预期", expected, actual);
}
}
=====================
参考
=====================
使用@SpringBootTest注解进行单元测试
https://www.cnblogs.com/ywjy/p/9997412.html
Testing in Spring Boot
https://www.baeldung.com/spring-boot-testing
第三十五章:SpringBoot与单元测试的小秘密
https://segmentfault.com/a/1190000011420910
http://tengj.top/2017/12/28/springboot12/
springboot(十二):springboot如何测试打包部署
http://www.ityouknow.com/springboot/2017/05/09/springboot-deploy.html
SpringBoot系列: 单元测试2的更多相关文章
- SpringBoot系列: 单元测试
SpringBoot 项目单元测试也很方便, Web项目中单元测试应该覆盖:1. Service 层2. Controller 层 本文前半部分讲解是一些测试基础配置. 对于Service和Contr ...
- Springboot 系列(十二)使用 Mybatis 集成 pagehelper 分页插件和 mapper 插件
前言 在 Springboot 系列文章第十一篇里(使用 Mybatis(自动生成插件) 访问数据库),实验了 Springboot 结合 Mybatis 以及 Mybatis-generator 生 ...
- springBoot系列教程07:异常捕获
发生异常是很正常的事,异常种类也是千奇百怪,发生异常并不可怕,只要正确的处理,并正确的返回错误信息并无大碍,如果不进行捕获或者处理,分分钟服务器宕机是很正常的事 所以处理异常时,最基本的要求就是发生异 ...
- SpringBoot系列——利用系统环境变量与配置文件的分支选择实现“智能部署”
前言 通过之前的博客:SpringBoot系列——jar包与war包的部署,我们已经知道了如果实现项目的简单部署,但项目部署的时候最烦的是什么?修改成发布环境对应的配置!数据库连接地址.Eureka注 ...
- Springboot 系列(十三)使用邮件服务
在我们这个时代,邮件服务不管是对于工作上的交流,还是平时的各种邮件通知,都是一个十分重要的存在.Java 从很早时候就可以通过 Java mail 支持邮件服务.Spring 更是对 Java mai ...
- Springboot 系列(九)使用 Spring JDBC 和 Druid 数据源监控
前言 作为一名 Java 开发者,相信对 JDBC(Java Data Base Connectivity)是不会陌生的,JDBC作为 Java 基础内容,它提供了一种基准,据此可以构建更高级的工具和 ...
- SpringBoot系列——Spring-Data-JPA(究极进化版) 自动生成单表基础增、删、改、查接口
前言 我们在之前的实现了springboot与data-jpa的增.删.改.查简单使用(请戳:SpringBoot系列——Spring-Data-JPA),并实现了升级版(请戳:SpringBoot系 ...
- SpringBoot系列——Spring-Data-JPA
前言 jpa是ORM映射框架,更多详情,请戳:apring-data-jpa官网:http://spring.io/projects/spring-data-jpa,以及一篇优秀的博客:https:/ ...
- SpringBoot系列——Spring-Data-JPA(升级版)
前言 在上篇博客中:SpringBoot系列——Spring-Data-JPA:https://www.cnblogs.com/huanzi-qch/p/9970545.html,我们实现了单表的基础 ...
随机推荐
- apache https配置【转】
博文来源:apache https配置 参考博文:apache.nginx配置自签名证书 1. 确认是否安装ssl模块 是否有mod_ssl.so文件 2. 生成证书和密钥 linux下 步骤1: ...
- kernel笔记——中断
cpu与磁盘.网卡.键盘等外围设备(相对于cpu和内存而言)交互时,cpu下发I/O请求到这些设备后,相对cpu的处理能力而言,磁盘.网卡等设备需要较长时间完成请求处理. 那么在请求发出到处理完成这段 ...
- Linux(CentOS)下设置nginx开机自动启动(2个办法)
首先,在linux系统的/etc/init.d/目录下创建nginx文件,使用如下命令: vim /etc/init.d/nginx 在脚本中添加如下命令: #!/bin/sh # # nginx - ...
- 两台主机,ssh端口不同,如何拷贝文件
A主机ip:172.26.225.199 ssh端口12995 B主机ip:172.26.225.200 ssh端口12991 将B主机的文件拷贝到A主机 [root@test2019030517 s ...
- js修改父子json格式成树状结构格式
js修改父子json成树状结构 var json = [ { "id" : "01", "pId":"" } , { & ...
- Entity Framework Core系列之DbContext
前言: EF Core DbContext表示与数据库的会话,并提供与数据库通信的API,具有以下功能: 数据库连接 数据操作,如查询和持久化 更改追踪 模型构建 数据映射 对象缓存 事务管理 数据库 ...
- 07-JavaScript之常用内置对象
JavaScript之常用内置对象 1.数组Array 1.1数组的创建方式 // 直接创建数组 var colors = ['red', 'blue', 'green']; console.log( ...
- 部署个人wordpress 笔记
yum list installed | grep php #检查当前安装的PHP包yum remove php.x86_64 php-cli.x86_64 php-common.x86_64 ... ...
- 4月11日java多线程4
继昨天学习了线程池之后,今天学习了多线程内的锁Lock. 定义方法: ReentrantLock queueLock = new ReentrantLock(); //可重入锁 ReentrantRe ...
- 为什么String被设计为不可变?是否真的不可变?
1 对象不可变定义 不可变对象是指对象的状态在被初始化以后,在整个对象的生命周期内,不可改变. 2 如何不可变 通常情况下,在java中通过以下步骤实现不可变 对于属性不提供设值方法 所有的属性定义为 ...