Spock让你爱上单元测试

2,167 阅读4分钟

Spock让你爱上单元测试

1. 为什么要写单元测试?

首先单元测试是为了提高代码的覆盖率,开发期间就覆盖并测试大部分业务场景,更快的发现和解决问题,提高代码质量,放心的交给其他人调用,从而减少上线前的心理负担。而不是直接交付到测试手中再提bug再打回来再反复测试,既耽误时间又浪费感情。

2. 为什么不想写单元测试?

第一个想到的肯定是麻烦,影响开发速度,本来1天能完成的工作因为要写单元测试可能1天就完不成了,又要mock数据,覆盖场景和用例越多单元测试的代码量也就越大,如果是按方法去写,写到最后可能比实际业务代码量还要多,最重要在这个敏捷为王的时代,确实影响心情,你问我写不写单元测试?我反正不写!

2. 什么是Spock?

Spock 是用于 Java 和 Groovy 应用程序的测试和规范框架,那什么是Groovy?Groovy是一种基于JVM(Java虚拟机)的敏捷开发语言,你可以粗糙的理解为它是跑在Jvm上的Python。

而Spock是一款拥有Groovy潇洒写法的单元测试框架,只需要一些依赖即可在java应用中使用groovy语言来编写单元测试。

要学习一门新语言?其实也不是,你只需要学习一点结构体就可以了,因为绝大部分逻辑依然是java,所以几乎没什么学习成本

3.和常用的Junit有什么区别?

更简单。并且天生支持mock。

4. 这里使用Springboot-Maven依赖Spock

在Springboot中使用需要依赖SpringbootTest,我这里依赖的是最新版本的Spock 2.0-groovy-3.0 , 2.0代表Spock版本,3.0是Groovy版本

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<!--引入spock-->
<dependency>
    <groupId>org.spockframework</groupId>
    <artifactId>spock-core</artifactId>
    <version>2.0-groovy-3.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.spockframework</groupId>
    <artifactId>spock-spring</artifactId>
    <version>2.0-groovy-3.0</version>
    <scope>test</scope>
</dependency>

还需要依赖一个单元测试插件

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
</plugin>
  1. 一些小示例

我们需要再单元测试root文件夹下创建Groovy文件而不是之前的java文件

image.png

创建一个基本的单元测试文件,加入了Spring上下文,可以直接注入依赖,Spock要求必须继承Specification类

@SpringBootTest(classes = TestApplication.class)
@ContextConfiguration
@ActiveProfiles(profiles = "dev")
class Test extends Specification{
    @Autowired
    Service service;
}

接下来写几个简单的单元测试,@Unroll注解可以将多次执行单独打印,方便查看结果

@Unroll
def "test add #a + #b = #result"(){
    expect: "测试"
    service.add(a,b) == result
    where: "条件"
    a | b | result
    1 | 2 | 3
    2 | 3 | 6
}

def 用来定义一个方法 ,后面跟上方法名字(不建议中文),expect: 定义了一个期望的行为,也可以理解为在这里调用一些service,我这里定义了一个add函数,返回它们的和

方法名上的# + 参数,是为了打印的时候方便看

service.add(a,b) == result , 意味着你传递了a,b两个参数并且判断结果等于result

where: "条件"
    a | b | result
    1 | 2 | 3
    2 | 3 | 6

where里列举了你需要测试的数据,和断言的结果,我们来执行一次看看

image.png

当你提供的数据和结果不符合时,它将提供一个非常友好的错误提示

image.png

当我们把返回值修改为正确的时候 image.png

其实看到这里,你已经可以开始编写单元测试了,后面会简单再介绍几种写法。

@Unroll
def "test not throw #a + #b"(){
    when:
       service.add(a, b)
    then: "条件"
       noExceptionThrown()
    where:
    a << [1, 2]
    b << [2, 3]
}

when then and 替代了上面的 expect, when 调用指定方法或函数 then 断言表达式 and 对上一个标签的补充

 where:
    a << [1, 2]
    b << [2, 3]

对于where他还有这样的写法,不用为了对齐表格而烦恼, 当执行完这两个数据之后没有抛出异常就算通过

反之也可以判断均抛出异常

when:
service.add(a, b)
then:
e = thrown(NullPointerException)
println(e)
where:
a << [null, 2]
b << [1, null]
e << [NullPointerException, NullPointerException]

也可以判断单元测试执行时间不能超过多少

@Unroll
@Timeout(unit = TimeUnit.SECONDS,value = 3)
def "test timeout #a + #b"(){
    expect:
    service.timeOut() // sleep 5秒
}

image.png

    @Shared 在多个方法中共享数据
    // 执行后执行类似于@After
    def cleanup(){
        orderId.clear();
    }
     // 执行前执行类似于@Before
    def setup(){
        orderId.clear();
    }

本文就只浅浅的介绍到这,更多使用方法参考Spock官网