Spock单元测试框架实战指南十 - 注意事项

1,429 阅读2分钟

Spock虽然好用,但要应用到实际项目中还是需要注意几个问题,下面讲下我们公司在使用过程中遇到的一些问题和解决方案

版本依赖

要使用Spock首先需要引入相关依赖,目前使用下来和我们项目兼容的Spock版本是1.3-groovy-2.5,以maven为例(gradle可以参考官网),完整的pom依赖如下:

<spock.version>1.3-groovy-2.5</spock.version>
<groovy.version>2.5.4</groovy.version>
 
<!-- spock -->
<dependency>
    <groupId>org.spockframework</groupId>
    <artifactId>spock-core</artifactId>
    <version>${spock.version}</version>
    <scope>test</scope>
</dependency>
<!-- spock和spring集成 -->
<dependency>
    <groupId>org.spockframework</groupId>
    <artifactId>spock-spring</artifactId>
    <version>${spock.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <scope>test</scope>
</dependency>
<!-- spock依赖的groovy -->
<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <type>pom</type>
    <version>${groovy.version}</version>
    <exclusions>
        <exclusion>
            <artifactId>groovy-test-junit5</artifactId>
            <groupId>org.codehaus.groovy</groupId>
        </exclusion>
        <exclusion>
            <artifactId>groovy-testng</artifactId>
            <groupId>org.codehaus.groovy</groupId>
        </exclusion>
    </exclusions>
</dependency>
 
<!--groovy 编译-->
<plugin>
    <groupId>org.codehaus.gmavenplus</groupId>
    <artifactId>gmavenplus-plugin</artifactId>
    <version>1.6</version>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
                <goal>compileTests</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Spock是使用groovy语言写单测的,所以需要引入groovy-all的依赖

在引入 groovy-all 包时排除了 groovy-test-junit5groovy-testng,这两个包和和 power mock 有冲突,在执行 mvn test 会导致NPE的问题

如果你的项目中没有用过groovy,还需要添加groovy的maven编译插件,这样才能编译我们用Spock写的单元测试

引入groovy依赖后可能会出现版本冲突的问题,因为如果你的项目引用了springboot-start-base这样的集合式jar包,它里面也会引用groovy,有可能跟我们引入的groovy包版本出现冲突,或者公司的一些框架也会引用groovy的包,如果版本不一致也有可能冲突,需要排下包

然后执行 mvn clean compile 验证下是否有冲突,如果能成功编译就没有这个问题

目前Spock的最新版本是2.0以上,在Spock 2.x 的版本里官方团队已经移除Sputnik,不再支持代理运行power mock的方式

因为Spock 2.0是基于JUnit5,我们项目以前的单元测试代码都是基于Junit4编写的,换成Junit5后,需要修改现有的java单测,比如指定代理运行,使用power mock的地方要换成Junit5的扩展语法

对现有使用Junit4 + power mock/jmockit的方式改变较大,为降低迁移成本没有使用最新的Spock2.X版本

如果你的项目之前就是使用Junit5写单测的,那么可以使用Spock2.X的版本,2.0以上版本使用power mock可以参考官方提供的解决方案:

(github.com/spockframew…)

后续我也会优先在我的博客 www.javakk.com 推出 Spock2.x 版本的使用教程

创建单元测试文件

编译(mvn clean compile)通过之后,用spock编写的groovy类型的单测代码不能放在原来的test/java目录下面

因为按照groovy的约定,默认编译groovy包下的单测,所以需要建个groovy文件夹存放spock的单测代码,如下图所示:

这样也方便区分原来Java单测和用Spock写的单测代码

另外记得别忘了标记groovy目录为测试源目录(Test Source Root),如下图:

(groovy文件夹右键 → Mark Directory as → Test Sources Root)

第一次运行spock单测代码时如果提示"no test suite exist"的错误,可以右键recompile下

还有记得创建的单测文件类型是Groovy Class,不是Java Class类型

最后使用intellij idea的快捷键创建单元测试,在需要测试的类或方法上右键IDE的菜单,选择"Go To → Create New Test" 选择我们已经创建好的groovy文件夹:

这样就自动生成了groovy类型的单测文件了

运行单元测试

执行 mvn test,按照上面两步的配置保证spock单测代码运行成功后可以执行 mvn clean test 命令,跑一下这个项目的单测用例

(这一步不是必须的,但如果公司加了单测覆盖率的统计时,在cicd系统发布时或merge request to release代码合并到release分支时,会先执行mvn test类似的指令,确保所有的单元测试运行成功)

如果你的项目和我们一样既有Java单测又有Spock单测,需要确保两种单测都能执行成功(目前我们项目的spock单测和java单测在公司的CICD系统以及git上都能兼容和通过测试覆盖率要求)

另外按照Spock的规范,单测代码文件的命名应该是以Spec为后缀的,如果你严格按照这个规范命名单测文件,比如"OrderServiceSpec.groovy",那么需要在maven-surefire-plugin测试插件里添加以Spec为后缀的配置:

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>${surefire.version}</version>
    <configuration>
        <includes>
            <include>**/*Spec.java</include>
            <include>**/*Test.java</include>
        </includes>
    </configuration>
</plugin>

但是我是直接使用IDE生成单元测试,intellij idea自动生成的单测后缀还是“Test”,所以不存在这个问题,如果你也是这样,可以忽略这个问题

单元测试规范

Spock虽然使用方便,但还是要遵循单元测试的规范来,比如单元测试一般是针对方法或类的维度去测试的,也就是说我们关注的重点是当前类或方法内部的逻辑

如果当前被测方法依赖了其他层或module的逻辑,最好mock掉,尽量不要跨层测试,这属于功能测试或集成测试的范畴

比如使用@SpringBootTest注解,默认会把当前方法依赖的下一层引用也注入进来,其实完全可以交给Spock去控制,可以不需要SpringBootTest

Spock和Mockito注解混用问题

因为Spock并不支持Mockito和power mock的@InjectMocks@Mock的组合,运行时会报错,如果你一定要使用对应的功能可以引入Mockitio为Spock专门开发的第三方工具:spock-subjects-collaborators-extension使用@Subject@Collaborator代替@InjectMocks@Mock

代码如下:

import spock.lang.Specification
import com.blogspot.toomuchcoding.spock.subjcollabs.Collaborator
import com.blogspot.toomuchcoding.spock.subjcollabs.Subject
 
class ConstructorInjectionSpec extends Specification {
 
    public static final String TEST_METHOD_1 = "Test method 1"
 
    SomeOtherClass someOtherClassNotToBeInjected = Mock()
 
    @Collaborator // 类似于Mockito的@Mock
    SomeOtherClass someOtherClass = Mock()
 
    @Subject // 类似于Mockito的@InjectMocks
    SomeClass systemUnderTest
 
    def "should inject collaborator into subject"() {
        given:
        someOtherClass.someMethod() >> TEST_METHOD_1
 
        when:
        String firstResult = systemUnderTest.someOtherClass.someMethod()
 
        then:
        firstResult == TEST_METHOD_1
        systemUnderTest.someOtherClass == someOtherClass
    }
 
    class SomeClass {
        SomeOtherClass someOtherClass
 
        SomeClass(SomeOtherClass someOtherClass) {
            this.someOtherClass = someOtherClass
        }
    }
 
    class SomeOtherClass {
        String someMethod() {
            "Some other class"
        }
    }
}

具体参考:

github.com/marcingrzej…

我个人的建议是用PowerMockito.mock()的方式代替注解,虽没有注解的语法简洁,但不用再引入额外的依赖

Power Mock参数匹配方法 Any()

如果在Spock里使用了power mockmock方法, 方法参数需要匹配的, 注意不要引用了spock的any()方法, 而应该使用power mock的any方法, 二者不能混用, 否则会报错

正确引用路径org.mockito.ArgumentMatchers

错误引用路径org.codehaus.groovy.runtime.DefaultGroovyMethods

**记得前提是在powermock的api里使用参数匹配,**如果是spock的mock方法,直接使用_下划线即可。

文章来源:javakk.com/322.html