在实际使用Spock的过程中如何把一些常用的测试方法抽出来,封装成基类使用。

BaseSpock

在前面几篇文章讲解Spock结合Power Mock实现静态方法mock功能时,示例代码里经常会用到LogUtils等工具类的静态方法去记录日志,那我们就可以把LogUtils类的mock代码抽到一个公共类中,然后我们的测试类去继承我们自己实现的公共类

比如我们把公共类起名叫BaseSpock.groovy文件,那么继承它的子类就拥有了模拟LogUtils静态方法的功能,而不用每个测试类单独去实现mock LogUtils日志的功能

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.spock.example

import com.spock.example.utils.LogUtils
import org.junit.runner.RunWith
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor
import org.powermock.modules.junit4.PowerMockRunner
import org.powermock.modules.junit4.PowerMockRunnerDelegate
import org.spockframework.runtime.Sputnik
import spock.lang.Specification

/**
* Spock基类
* @author jinglv* @date 2021/7/30 11:34 上午
*/
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
@PrepareForTest([LogUtils.class])
@SuppressStaticInitializationFor(["com.spock.example.utils.LogUtils"])
class BaseSpock extends Specification {
void setup() {
println "Spock setup"
// mock掉一些项目中常用的类,比如日志记录
PowerMockito.mockStatic(LogUtils.class)
}
}

BaseSpock是我们封装的spock基类,它继承Specification,在setUp方法内部对LogUtils进行了mock

BaseSpock可以放在一个公共的项目中或作为jar的方式引用,也可以放在src/main/groovy/下面作为一个公共类调用

image-20210730113939577

然后原来用到LogUtils日志类的单元测试可以继承BaseSpock基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.spock.example.service

import com.spock.example.BaseSpock
import com.spock.example.dao.UserDAO
import com.spock.example.dto.UserDTO
import com.spock.example.utils.IDNumberUtils
import org.mockito.Mockito
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest

/**
* @author jinglv* @date 2021/7/28 4:24 下午
*/
@PrepareForTest([IDNumberUtils.class])
class UserServiceStaticTest extends BaseSpock {

def processor = new UserService()
def dao = Mock(UserDAO)

void setup() {
processor.userDAO = dao
// mock静态类
// PowerMockito.mockStatic(LogUtils.class)
PowerMockito.mockStatic(IDNumberUtils.class)
}

def "GetUserByIdStatic"() {
given: "设置请求参数"
def user1 = new UserDTO(id: 1, name: "张三", province: "上海")
def user2 = new UserDTO(id: 2, name: "李四", province: "江苏")
def idMap = ["birthday": "1992-09-18", "sex": "男", "age": "28"]

and: "mock掉接口返回的用户信息"
dao.getUserInfo() >> [user1, user2]

and: "mock静态方法返回值"
PowerMockito.when(IDNumberUtils.getBirAgeSex(Mockito.any())).thenReturn(idMap)

when: "调用获取用户信息方法"
def response = processor.getUserByIdStatic(1)

then: "验证返回结果是否符合预期值"
with(response) {
name == "张三"
abbreviation == "沪"
postCode == 200000
age == 28
}
}
}

如果你除了LogUtils这些常用的类需要mock外,还需要mock其他的静态方法的话,使用前面介绍的spock结合power mock的用法即可,类似下面这样写:

image-20210730114142206

当前的单元测试类需要mock IDNumberUtils类,可以使用@PrepareForTest注解,这样既可以使用基类mock LogUtils日志的功能,也可以给自己的单测类增加新的静态方法mock功能

注意事项

  1. BaseSpock的类型是groovy文件,这个是因为Spock内置的测试引擎在启动时会检查继承它的子类是否是groovy类型的文件,所以如果你要封装一个类似BaseSpock的基类,文件后缀不能是 .java的,必须是 .groovy的类型
  2. 所有的Spock单测类不能有自己的构造函数,因为单元测试的实例都是由Spock创建和管理的

Spock单元测试代码的运行顺序是:

setupSpec() → setup() → cleanup() → cleanupSpec()

Spock中的代码块和JUnit对应关系

Spock JUnit
Specification Test class
setup() @Before
cleanup() @After
setupSpec() @BeforeClass
cleanupSpec() @AfterClass
Feature Test
Feature method Test method
Data-driven feature Theory
Condition Assertion
Exception condition @Test(expected=…)
Interaction Mock expectation(e.g. in Mockito)