本来打算是把Spock的使用写成一篇的,后来发现太长了而且结构比较冗杂,还是拆分出来比较好,而且这样也比较好检索。初次使用,如果有误,请轻喷,并指出问题。我会及时纠正。
要使用Spock,必须要有Junit和Mock的基本概念和使用。建议在使用Spock之前先懂了解一下Junit和Mockitio
下一篇博客:《SpringBoot+Spock的熟悉之路(二):Spock,Mock和Mockitio的关系》
简而言之,通过Groovy脚本语言一些具有特色的语法,可以让我们在写测试类的时候写起来更方便,更快,读起来看着代码也能稍微简洁易懂些(当然是对于熟悉这套框架语法的人而言)
对于Java程序员来讲,Groovy语言非常友好,我们甚至可以在Groovy类里完全写java代码(不过这样使用Groovy就没啥意义了)或者混合写代码而不会报错(我还没遇到过)。
一定要注意版本的问题!一定要注意版本的问题!一定要注意版本的问题!
Tool | Version |
---|---|
Intellij IDEA | 2018.3 Ultimate |
SpringBoot | 2.0.1 |
Java | 1.8 |
mybatis-spring-boot | 2.0.1 |
Groovy | 2.4.6 |
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<!--服务器配置的是Oracle-->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc8</artifactId>
<version>12.2.0.1</version>
</dependency>
<!---------------Spock必须的------------------->
<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>
<version>1.3-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.3-RC1-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.6</version>
</dependency>
<!-------------- 可选项 ----------------->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency> <!-- 允许MockInterface等 -->
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.9.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>2.6</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
可以下一个Spock的Idea插件,让IDEA支持Spock的一些高亮和自动创建等功能
叫这个名字 Spock Framework Enhancements
如果一开始创建SpringBoot项目的时候勾选了test,那么IDEA会自动帮你创建一个test目录。如果想要后续添加test目录的朋友,请按照SpringBoot推荐的结构进行创建——即和main里的项目结构保持高度一致
resources是我自己新创建的文件夹,之后准备用来存放测试类的配置信息,要让IDEA识别它,点击最上面菜单栏里的File->Project Structure,点击Module,分别把resources标注成Test Resources
先熟悉一下Spock的语法和结构,单纯的看看Spock特定,熟悉之后再去结合咱们项目的结构
先创建测试类。右键点击new -> Spock Specification。点击那个Groovy Class也可以,只不过需要手动去extends Specification
自动创建的Spock类如下
package com.example.demo.simple
import spock.lang.Specification
class SimpleTest extends Specification {
}
Spock测试类的一大特征就是必须要extends Specification
对比Junit,Spock有一些特点有类似的作用
Spock | Junit |
---|---|
Specification | Test Class |
setup() | @Before |
cleanup() | @After |
setupSpec() | @BeforeClass |
cleanupSpec() | @AfterClass |
list<<“a”<<“b” | list.add(“a”);list.add(“b”) |
list.size() == 2 | Assert.equals(list.size(),2) |
一个Spock测试类的结构大体上如下
@Title("测试的标题")
@Narrative("""关于测试的大段文本描述""")
class TestCaseClass extends Specification {
@Shared //在测试方法之间共享的数据
SomeClass sharedObj
def setupSpec() {
//TODO: 设置每个测试类的环境
}
def setup() {
//TODO: 设置每个测试方法的环境,每个测试方法执行一次
}
@Ignore("忽略这个测试方法")
def "测试方法1" () {
given: "给定一个前置条件"
//TODO: code here
and: "其他前置条件"
expect: "随处可用的断言"
//TODO: code here
when: "当发生一个特定的事件"
//TODO: code here
and: "其他的触发条件"
then: "产生的后置结果"
//TODO: code here
and: "同时产生的其他结果"
where: "不是必需的测试数据"
input1 | input2 || output
... | ... || ...
}
@IgnoreRest //只测试这个方法,而忽略所有其他方法
@Timeout(value = 50, unit = TimeUnit.MILLISECONDS) // 设置测试方法的超时时间,默认单位为秒
def "测试方法2"() {
//TODO: code here
}
def cleanup() {
//TODO: 清理每个测试方法的环境,每个测试方法执行一次
}
def cleanupSepc() {
//TODO: 清理每个测试类的环境
}
实体类
package com.example.demo.entity;
import lombok.Data;
@Data
public class DemoEntity {
private Integer number;
private String str;
}
Spock测试类
package com.example.demo.simple
import com.example.demo.entity.DemoEntity
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class SimpleSpec extends Specification {
//此注解标明该变量可以被下面所有测试类共享
@Shared
DemoEntity demoEntity
def setup(){
demoEntity = new DemoEntity()
}
def "simple test1"(){
given:
demoEntity.number = 1
def result
//expect,随处可用的断言,如果这里没有通过,则不会执行下面的when及之后的代码
expect:
++demoEntity.number == 2
when:
result = demoEntity.number*2
then:
result == 4
}
//加了Unroll注解后,where里面的每一条数据是一次测试方法。#str可以动态拼接下面的str值来组成不同的方法名
@Unroll
def "simple test2 on check #str"(){
given:
demoEntity.str = str
expect:
check()
//可以通过where方式来批量定义数据,不仅可以定义属性值,也可以定义方法
where:
str| check
"yes" | {
return true}
"no" | {
return false}
"0" | {
throw new Exception("don't support number")}
}
}
simple test1 好理解,有两次判断,expect那里有一个判断,最后的then那里有一个判断,两次通过后,该条测试方法才算通过
simple test2执行后可以看到控制台的结果是
看最后的一条的报错是
可以在类上面加上一个注解@Stepwise
它的作用是强制整个测试类按照定义顺序执行。
@Stepwise
class SimpleSpec extends Specification {
}
换个说法,当点simple test 2的时候,simple test 1也会强制执行
通过上面的例子,至少可以看出Spock有以下几个特点
src
里也有不少例子可以查看本模块主要是把自己踩过的一些比较大的坑给单独列举出来
一开始我以为用Groovy的时候一定要在本地装Groovy环境,装了之后发现控制台老实说config文件的第一行被注释过的一行文字有错。虽然此错误不影响程序运行,但对于有强迫症的我来说实在是不爽。
后来查资料说是依赖里的groovy的版本和本地环境的版本不一致。后来就算配成了一样的还是报那个错误。
最后进行尝试发现,虽然要使用Groovy,但我们Java程序员在使用Intellij IDEA的条件下不需要在系统下载并安装Groovy的环境,也不用给Idea设置Groovy的环境变量。 我把本地装的Groovy卸载后,就没有报版本错误的问题。
如果必须要在本地配Groovy的朋友有遇到并解决过这个问题的,麻烦赐教一下怎么解决的
当启动一个Specification的时候,如果控制台出现了这种问题的时候
请确认一下自己的Groovy的版本是否为2.5及以上
注意,虽然Groovy现在已经到了2.5了,而且官网上确实也用的是version2.5.7,但是SpringBoot对Groovy2.5的支持貌似有点问题。其结果就是引入了groovy2.5依赖后,groovy编译报internal error:code 1这种不明所以的错误
我目前没有找到比较合适的办法,只能将Groovy回退到2.4版本,如果有找到方法的朋友,麻烦赐教一下,谢谢。
SpringBoot官方issue
当遇到这种错误的时候
这个错误是Junit报的,证明这个类并没有被识别为Spock测试类,而是使用的Junit Test或者其他方式启动的。Junit对测试类的识别方法是是否有@Test注解。看一下自己的测试类上是不是加了类似于这种东西
Spock框架不要这些@Runwith注解。在Specification类里就已经有了此注解
Spock in Java 慢慢爱上写单元测试
spock-testing-exceptions-with-data-tables
Spock官方文档
Spock开源GitHub