Spock单元测试框架系列(二):Spock使用详解
在上一篇中,我们介绍了什么是Spock以及它和JUnit,JMock等其它测试框架的对比,从这一篇开始,我们将开始详细介绍Spock的使用。
在这一章,我们首先讲解如何在项目中引入Spock,以及Spock的一些核心概念介绍,并且抛砖引玉给出一个简单的单测示例。
引入Spock
- 环境
针对Java单元测试,开发工具 IntelliJ IDEA。
- Maven依赖
<!--
可以只引入这一个包,它已经引入了 spock-core、groovy-all; 如果有冲突可以手动指定版本引入
此包是整合springboot使用,用于在spock中启动springboot容器,
如果仅想使用spock作为单测使用,可以不引入当前包,只引入 spock-core、groovy-all 即可
-->
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.3-groovy-2.4</version>
</dependency>
<!-- 可以不引入 -->
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.3-groovy-2.4</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>2.4.17</version>
</dependency>
- 插件配置
在单测用例所在module的 POM文件添加plugin的配置,多module的情况下放主pom不生效
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<skipTests>false</skipTests>
<parallel>classes</parallel>
<threadCount>4</threadCount>
<forkCount>1C</forkCount>
<includes>
<include>**/*Test.java</include>
<include>**/*Spec.java</include>
</includes>
</configuration>
</plugin>
<plugin>
<!--groovy plugin-->
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.4</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- spock单测文件路径 -->
<testSources>
<testSource>
<directory>${project.basedir}/src/test/java</directory>
<includes>
<include>**/*.groovy</include>
</includes>
</testSource>
<testSource>
<directory>${project.basedir}/src/test/groovy</directory>
<includes>
<include>**/*.groovy</include>
</includes>
</testSource>
</testSources>
</configuration>
</plugin>
- 引入过程中的注意点
问题1:
java.lang.NoClassDefFoundError:
org/codehaus/groovy/transform/stc/AbstractExtensionMethodCache
解决:将groovy 和 groovy-all 都保持2.4.17版本
问题2:配置了gmavenplus-plugin并指定了路径但不生效
配置在主pom中,但单测在B模块中导致,将插件配置到A或B模块中
主pom
|----A
|----B
问题3:引入groovy依赖后可能会出现版本冲突的问题
因为如果你的项目引用了springboot-start-base这样的集合式jar包,它里面也会引用groovy,有可能跟我们引入的groovy包版本出现冲突,或者公司的一些框架也会引用groovy的包,如果版本不一致也有可能冲突,需要排下包。
这里推荐idea的maven插件 MavenHelper 能可视化快速排查依赖问题。
Spock核心概念
- Spock标签
标签 | 作用 | 备注 |
given | 为所测试方法做一些前置准备工作的地方 | 一般将方法内部依赖的参数在这个标签内完成 |
when | 执行测试 | when 和 then 必须成对出现 |
then | 验证测试结果是否符合预期 | when 和 then 必须成对出现 |
expect | 精简版when+then | expect里代码只可以包含布尔表达式 |
and | 承接上一个标签 | |
where | 用于编写数据驱动的用例方法 | 总是在方法的最后,且不能重复 |
cleanup | 测试方法退出前执行一些清理工作 | 一个 cleanup 后仅仅只能跟一个where ,即使前面发生了异常cleanup也会运行(类似于Java的finally) |
- Mock、Stub和Spy区别
方法 | 描述 |
Mock() | Mock的对象是一个虚拟类,为每个方法调用返回了一个默认值,用于替换真实的类。Mock()不仅可以模拟方法返回结果,还可以模拟方法行为,比如验证某个方法是否被调用以及调用次数 |
Stub() | Stub的对象也是一个虚拟类,比Mock()更简单些。只返回事先准备好的测试数据,而不提供交互验证 |
Spy() | 又叫刺探方法,它会包装一个真实的对象,默认情况下将调用真实的方法。Spy()功能最强大,但这样做意味着代码可能有坏味道,设计上可能有问题。 |
Spock单测示例
待测试类:
public class Square {
private final int length;
public Square(int length) {
this.length = length;
}
public int area() {
return length * length;
}
public int perimeter() {
return length * 4;
}
}
测试类:
class SquareTest extends Specification {
@Unroll
def "given square length: #length, square area: #area, square perimeter: #perimeter"() {
given:
def square = new Square(
length: length
)
expect:
square.area() == area
square.perimeter() == perimeter
where:
length || area | perimeter
1 || 1 | 4
2 || 4 | 8
}
}
def 是groovy的关键字,可以用来定义变量和方法名,这里可以用英文也可以用中文。
expect...where... expect为核心的测试校验语句块,where则是多个测试用例的举例,多个列使用"|"单竖线隔开,"||"双竖线区分输入和输出变量,即左边是输入值,右边是输出值。表格列的长度不一样,手动对齐比较麻烦,intellij idea支持format格式化快捷键。
上面图中定义的测试方法名使用了groovy的字面值特性,即把请求参数值和返回结果值的字符串动态替换掉,"#length、#area、#perimeter" #号后面的变量是在方法内部定义的,前面加上#号,实现占位符的功能。
@Unroll 注解,可以把每一次调用作为一个单独的测试用例运行,这样运行后的单测结果更直观:
如果其中一行测试结果错误,spock的错误提示信息也很详细,方便我们排查(比如我们把第2行测试用例返回的perimeter改成4)
总结
在这篇文章中,我们引入了Spock,并对Spock的一些概念做了简单介绍,最后给出一个简单的测试示例,进一步加深对Spock的理解。下一篇章我们将会详细的介绍如何通过Spock测试复杂的if-else场景。