在Spring Boot项目中使用Spock框架
转载:https://www.jianshu.com/p/f1e354d382cd
Spock框架是基于Groovy语言的测试框架,Groovy与Java具备良好的互操作性,因此可以在Spring Boot项目中使用该框架写优雅、高效以及DSL化的测试用例。Spock通过@RunWith注解与JUnit框架协同使用,另外,Spock也可以和Mockito(Spring Boot应用的测试——Mockito)协同使用。
在这个小节中我们会利用Spock、Mockito一起编写一些测试用例(包括对Controller的测试和对Repository的测试),感受下Spock的使用。
How Do
- 根据Building an Application with Spring Boot这篇文章的描述,spring-boot-maven-plugin这个插件同时也支持在Spring Boot框架中使用Groovy语言。
- 在pom文件中添加Spock框架的依赖
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<scope>test</scope></dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<scope>test</scope>
</dependency>
- 在src/test目录下创建groovy文件夹,在groovy文件夹下创建com/test/bookpub包。
- 在resources目录下添加packt-books.sql文件,内容如下所示:
INSERT INTO author (id, first_name, last_name) VALUES (5, 'Shrikrishna', 'Holla');
INSERT INTO book (isbn, title, author, publisher) VALUES ('978-1-78398-478-7', 'Orchestrating Docker', 5, 1);
INSERT INTO author (id, first_name, last_name) VALUES (6, 'du', 'qi');
INSERT INTO book (isbn, title, author, publisher) VALUES ('978-1-78528-415-1', 'Spring Boot Recipes', 6, 1);
- 在com/test/bookpub目录下创建SpockBookRepositorySpecification.groovy文件,内容是:
package com.test.bookpubimport com.test.bookpub.domain.Author
import com.test.bookpub.domain.Book
import com.test.bookpub.domain.Publisher
import com.test.bookpub.repository.BookRepository
import com.test.bookpub.repository.PublisherRepository
import org.mockito.Mockito
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.SpringApplicationContextLoader
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.web.WebAppConfiguration
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import spock.lang.Sharedimport spock.lang.Specification
import javax.sql.DataSourceimport javax.transaction.Transactional
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebAppConfiguration
@ContextConfiguration(classes = [BookPubApplication.class,
TestMockBeansConfig.class],loader = SpringApplicationContextLoader.class)
class SpockBookRepositorySpecification extends Specification {
@Autowired
private ConfigurableApplicationContext context;
@Shared
boolean sharedSetupDone = false;
@Autowired
private DataSource ds;
@Autowired
private BookRepository bookRepository;
@Autowired
private PublisherRepository publisherRepository;
@Shared
private MockMvc mockMvc;
void setup() {
if (!sharedSetupDone) {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
sharedSetupDone = true;
}
ResourceDatabasePopulator populator = new
ResourceDatabasePopulator(context.getResource("classpath:/packt-books.sql"));
DatabasePopulatorUtils.execute(populator, ds);
}
@Transactional
def "Test RESTful GET"() {
when:
def result = mockMvc.perform(get("/books/${isbn}"));
then:
result.andExpect(status().isOk())
result.andExpect(content().string(containsString(title)));
where:
isbn | title
"978-1-78398-478-7"|"Orchestrating Docker"
"978-1-78528-415-1"|"Spring Boot Recipes"
}
@Transactional
def "Insert another book"() {
setup:
def existingBook = bookRepository.findBookByIsbn("978-1-78528-415-1")
def newBook = new Book("978-1-12345-678-9", "Some Future Book",
existingBook.getAuthor(), existingBook.getPublisher())
expect:
bookRepository.count() == 3
when:
def savedBook = bookRepository.save(newBook)
then:
bookRepository.count() == 4
savedBook.id > -1
}
}
- 执行测试用例,测试通过
- 接下来试验下Spock如何与mock对象一起工作,之前的文章中我们已经在TestMockBeansConfig类中定义了PublisherRepository的Spring Bean,如下所示,由于@Primary的存在,使得在运行测试用例时Spring Boot优先使用Mockito框架模拟出的实例。
@Configuration
@UsedForTesting
public class TestMockBeansConfig {
@Bean
@Primary
public PublisherRepository createMockPublisherRepository() {
return Mockito.mock(PublisherRepository.class);
}
}
- 在BookController.java中添加getBooksByPublisher接口,代码如下所示:
@Autowired
public PublisherRepository publisherRepository;
@RequestMapping(value = "/publisher/{id}", method = RequestMethod.GET)
public List<Book> getBooksByPublisher(@PathVariable("id") Long id) {
Publisher publisher = publisherRepository.findOne(id);
Assert.notNull(publisher);
return publisher.getBooks();
}
- 在SpockBookRepositorySpecification.groovy文件中添加对应的测试用例,
def "Test RESTful GET books by publisher"() {
setup:
Publisher publisher = new Publisher("Strange Books")
publisher.setId(999)
Book book = new Book("978-1-98765-432-1",
"Mytery Book",
new Author("Jhon", "Done"),
publisher)
publisher.setBooks([book])
Mockito.when(publisherRepository.count()).
thenReturn(1L);
Mockito.when(publisherRepository.findOne(1L)).
thenReturn(publisher)
when:
def result = mockMvc.perform(get("/books/publisher/1"))
then:
result.andExpect(status().isOk())
result.andExpect(content().string(containsString("Strange Books")))
cleanup:
Mockito.reset(publisherRepository)
}
- 运行测试用例,发现可以测试通过,在控制器将对象转换成JSON字符串装入HTTP响应体时,依赖Jackson库执行转换,可能会有循环依赖的问题——在模型关系中,一本书依赖一个出版社,一个出版社有包含多本书,在执行转换时,如果不进行特殊处理,就会循环解析。我们这里通过@JsonBackReference注解阻止循环依赖。
分析
可以看出,通过Spock框架可以写出优雅而强大的测试代码。
首先看SpockBookRepositorySpecification.groovy文件,该类继承自Specification类,告诉JUnit这个类是测试类。查看Specification类的源码,可以发现它被@RunWith(Sputnik.class)注解修饰,这个注解是连接Spock与JUnit的桥梁。除了引导JUnit,Specification类还提供了很多测试方法和mocking支持。
Note:关于Spock的文档见这里:Spock Framework Reference Documentation
根据《单元测试的艺术》一书中提到的,单元测试包括:准备测试数据、执行待测试方法、判断执行结果三个步骤。Spock通过setup、expect、when和then等标签将这些步骤放在一个测试用例中。
- setup:这个块用于定义变量、准备测试数据、构建mock对象等;
- expect:一般跟在setup块后使用,包含一些assert语句,检查在setup块中准备好的测试环境
- when:在这个块中调用要测试的方法;
- then : 一般跟在when后使用,尽可以包含断言语句、异常检查语句等等,用于检查要测试的方法执行后结果是否符合预期;
- cleanup:用于清除setup块中对环境做的修改,即将当前测试用例中的修改回滚,在这个例子中我们对publisherRepository对象执行重置操作。
Spock也提供了setup()和cleanup()方法,执行一些给所有测试用例使用的准备和清除动作,例如在这个例子中我们使用setup方法:(1)mock出web运行环境,可以接受http请求;(2)加载packt-books.sql文件,导入预定义的测试数据。web环境只需要Mock一次,因此使用sharedSetupDone这个标志来控制。
通过@Transactional注解可以实现事务操作,如果某个方法被该注解修饰,则与之相关的setup()方法、cleanup()方法都被定义在一个事务内执行操作:要么全部成功、要么回滚到初始状态。我们依靠这个方法保证数据库的整洁,也避免了每次输入相同的数据。
作者:杜琪
链接:https://www.jianshu.com/p/f1e354d382cd
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
在Spring Boot项目中使用Spock框架的更多相关文章
- 在Spring Boot项目中使用Spock测试框架
本文首发于个人网站:在Spring Boot项目中使用Spock测试框架 Spock框架是基于Groovy语言的测试框架,Groovy与Java具备良好的互操作性,因此可以在Spring Boot项目 ...
- Spring Boot项目中如何定制拦截器
本文首发于个人网站:Spring Boot项目中如何定制拦截器 Servlet 过滤器属于Servlet API,和Spring关系不大.除了使用过滤器包装web请求,Spring MVC还提供Han ...
- Spring Boot项目中如何定制PropertyEditors
本文首发于个人网站:Spring Boot项目中如何定制PropertyEditors 在Spring Boot: 定制HTTP消息转换器一文中我们学习了如何配置消息转换器用于HTTP请求和响应数据, ...
- Spring Boot项目中如何定制servlet-filters
本文首发于个人网站:Spring Boot项目中如何定制servlet-filters 在实际的web应用程序中,经常需要在请求(request)外面增加包装用于:记录调用日志.排除有XSS威胁的字符 ...
- Spring Boot项目中使用Mockito
本文首发于个人网站:Spring Boot项目中使用Mockito Spring Boot可以和大部分流行的测试框架协同工作:通过Spring JUnit创建单元测试:生成测试数据初始化数据库用于测试 ...
- 你真的理解 Spring Boot 项目中的 parent 吗?
前面和大伙聊了 Spring Boot 项目的三种创建方式,这三种创建方式,无论是哪一种,创建成功后,pom.xml 坐标文件中都有如下一段引用: <parent> <groupId ...
- Spring Boot项目中使用Swagger2
Swagger2是一款restful接口文档在线生成和在线接口调试工具,Swagger2在Swagger1.x版本的基础上做了些改进,下面是在一个Spring Boot项目中引入Swagger2的简要 ...
- Spring Boot2 系列教程(三)理解 Spring Boot 项目中的 parent
前面和大伙聊了 Spring Boot 项目的三种创建方式,这三种创建方式,无论是哪一种,创建成功后,pom.xml 坐标文件中都有如下一段引用: <parent> <groupId ...
- Spring Boot 项目中的 parent
前言 我们成功创建Spring Boot之后,pom.xml坐标文件中都会有如下一段引用: <parent> <groupId>org.springframework.boot ...
随机推荐
- 【初探移动前端开发04】jQuery Mobile 一
网格布局 jquery mobile提供一种多列布局功能,由于移动设备的屏幕大小原因,一般情况还是不要使用多列布局啦. jquery mobile提供一种css样式规则来定义多列布局,对应css为ui ...
- QT库在Windows上的编译
1.从http://www.qtcentre.org/下载QT源代码,注意是源代码.我下载到的是QT4.5.1版,文件名为qt-win-opensource-src-4.5.1.zip: 2.解压,注 ...
- 洛谷P3385 [模板]负环 [SPFA]
题目传送门 题目描述 暴力枚举/SPFA/Bellman-ford/奇怪的贪心/超神搜索 输入输出格式 输入格式: 第一行一个正整数T表示数据组数,对于每组数据: 第一行两个正整数N M,表示图有N个 ...
- Java浮点类型的格式化
概述 基于Java,介绍将浮点类型小数进行格式化的方案. 正文 在Java中,用于格式化小数的类是java.text.DecimalFormat,比如你可以这样使用: double data = 33 ...
- Linux命令之chmod
chmod [选项] … MODE[,MODE] … FILE … chmod [选项] … OCTAL-MODE FILE … chmod [选项] … --reference=RFILE FILE ...
- 第3天:YAML语法
YAML是一种可读性很强的数据格式语言.正是由于YAML良好的可读性,其广泛引用于软件配置中. 语法规则 YAML文件中的第一行为"---",表示这是一个YAML文件: YAML中 ...
- HihoCoder - 1715 树的连通问题
题面在这里! 正式告别文化课回归的第一题QWQ,然鹅半个月之后还是要退役QWQWQWQWQ 好像很久之前就见过的一个题,当时只会打一打 O(N^2) 的暴力QWQ,正好今天又写了遍这个暴力用来对拍23 ...
- [DesignPattern]Builder设计模式
模式的定义 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 模式的使用场景 相同的方法,不同的执行顺序,产生不同的事件结果时: 多个部件或零件,都可以装配到一个对象中,但是 ...
- Java学习笔记(9)
final关键字; (修饰符) final关键字的用法: final关键字修饰一个基本类型的变量时,该变量不能重新赋值,第一次的值为最终的. final关键字修饰一个引用类型变量时,该变量不能重新指 ...
- [bzoj1009](HNOI2008)GT考试 (kmp+矩阵快速幂加速递推)
Description 阿 申准备报名参加GT考试,准考证号为N位数X1X2....Xn(0<=Xi<=9),他不希望准考证号上出现不吉利的数字.他的不吉利数学 A1A2...Am(0&l ...