groovy测试框架-Spock入门

简介

为什么要用Spock  

一、入门

1.1 依赖引入

1.2.定义一个Spock测试类

1.3一个简单的测试方法 

1.4With与VerifyAll

二、Mock

三、Mock,stub,spy

四、参考文献


简介

       Spock 是用于 Java 和 Groovy 应用程序的测试和规范框架。使它从人群中脱颖而出的是其美丽且极具表现力的规范语言。由于其 JUnit 运行器,Spock 与大多数 IDE、构建工具和持续集成服务器兼容。Spock 的灵感来自JUnit、 jMockRSpecGroovyScala、 Vulcans和其他迷人的生命形式。

为什么要用Spock  

        总的来说,JUnit、jMock、Mockito都是相对独立的工具,只是针对不同的业务场景提供特定的解决方案。其中JUnit单纯用于测试,并不提供Mock功能。

        我们的服务大部分是分布式微服务架构。服务与服务之间通常都是通过接口的方式进行交互。即使在同一个服务内也会分为多个模块,业务功能需要依赖下游接口的返回数据,才能继续后面的处理流程。这里的下游不限于接口,还包括中间件数据存储比如Squirrel、DB、MCC配置中心等等,所以如果想要测试自己的代码逻辑,就必须把这些依赖项Mock掉。因为如果下游接口不稳定可能会影响我们代码的测试结果,让下游接口返回指定的结果集(事先准备好的数据),这样才能验证我们的代码是否正确,是否符合逻辑结果的预期。

        尽管jMock、Mockito提供了Mock功能,可以把接口等依赖屏蔽掉,但不能对静态方法Mock。虽然PowerMock、jMockit能够提供静态方法的Mock,但它们之间也需要配合(JUnit + Mockito PowerMock)使用,并且语法上比较繁琐。工具多了就会导致不同的人写出的单元测试代码“五花八门”,风格相差较大。

        Spock通过提供规范性的描述,定义多种标签(givenwhenthenwhere等),去描述代码“应该做什么”,“输入条件是什么”,“输出是否符合预期”,从语义层面规范了代码的编写。

        Spock自带Mock功能,使用简单方便(也支持扩展其他Mock框架,比如PowerMock),再加上Groovy动态语言的强大语法,能写出简洁高效的测试代码,同时能方便直观地验证业务代码的行为流转,增强工程师对代码执行逻辑的可控性。

一、入门

1.1 依赖引入

        <dependency>
            <groupId>org.spockframework</groupId>
            <artifactId>spock-core</artifactId>
            <version>1.0-groovy-2.4</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy</artifactId>
            <version>2.4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.4.12</version>
            <type>pom</type>
            <scope>test</scope>
        </dependency>
        <dependency> <!-- enables mocking of classes (in addition to interfaces) -->
            <groupId>cglib</groupId>
            <artifactId>cglib-nodep</artifactId>
            <version>3.1</version>
            <scope>test</scope>
        </dependency>

1.2.定义一个Spock测试类

创建类的时候选择Groovy Class 

class MyFirstSpec extends Specification {
....

}

      类Specification包含许多用于编写规范的有用方法。此外,它指示 JUnit 使用SputnikSpock 的 JUnit运行器运行规范。多亏了 Sputnik,大多数现代 Java IDE 和构建工具都可以运行 Spock 规范。

     我们在给测试类命名时通常以Specification或Spec结尾,以标识出类为Spock测试类。

常用方法介绍:

def setupSpec() {} // 运行一次 - 在第一个def方法运行之前

setup() {} // 在每个def方法运行之前运行

cleanup() {} // 在每个def方法运行之后运行

cleanupSpec() {} // 运行一次 - 在最后一个def方法运行之后

Junit与Spock方法的一个映射关系

1.3一个简单的测试方法 

class MyFirstSpec extends Specification {

    /**
     * @Shared 将变量定义为共享变量
     */
    @Shared data="testseeee"

    /**
     * 在每个def方法运行前给data赋值
     */
    void setup(){
        data="aaaaa"
    }

    /**
     * 测试方法
     * @return
     */
    def "use when"(){
        //初始化数据
        given:"initData"
        def a=1
        def b=2

        //运行
        when:"exec"
        def x=Math.max(a,b)

        //校验
        then:"assert"
        x==2
        data=="aaaaa"
    }

    def "use expect"(){
        /**
         * 运行并校验
         */
        expect:""
        Math.max(1,2)==2
    }

    /**
     * 异常捕获,相比较与try-catch,spock中有了新的异常捕获方式
     * thrown()
     * 通过使用notThrown(),表示不应抛出的异常
     */
    def "stackExceptionCatch"(){
        given:"initData"
        def stack=new Stack()

        when:"exec"
        stack.pop()

        then:"assert"
        def e=thrown(EmptyStackException)
        e.cause==null

    }

    /**
     * where块的使用,可用于数据驱动的测试
     * @Unroll 对于where的每个情况都生成一个单测用例,不加该注解则认为这两种情况只是一个单测示例
     * #a |#b ||#c 用于输出参数值,让单测示例更易看
     */
    @Unroll
    def "math max use where #a |#b ||#c"(){

        expect:"exec"
        c==Math.max(a,b)

        where:"assert"
        a|b||c
        1|2||2
        3|2||3
    }
}

       Spock 内置支持实现功能方法的每个概念阶段。为此,特征方法被构造成所谓的。块以标签开始,并延伸到下一个块的开头,或方法的结尾。有6种模块:givenwhenthenexpectcleanup,和where块。方法开头和第一个显式块之间的任何语句都属于隐式given块。

     下图演示了块如何映射到特征方法的概念阶段。该where区块有一个特殊的作用,很快就会揭晓。但首先,让我们仔细看看其他块。

块名作用说明
given输入条件(前置参数)前面不能有其他块,也不能重复。一个given块不具有任何特殊的语义。该given:标签是可选的并且可以省略,导致隐式 given块。最初,别名setup:是首选的块名称,但使用given:通常会导致更易读的功能方法描述(参见规范作为文档)。
when执行行为whenthen块总是一起出现。他们描述了一种刺激和预期的反应。虽然when 块可以包含任意代码,但then块仅限于条件异常条件交互和变量定义。一个特征方法可能包含多对when-then块。
then输出条件,验证结果
and衔接上个标签,补充作用
expect
类似when+then的结合一个expect块被比较有限then的,因为它可能只包含条件和变量定义块。在更自然地用单个表达式描述刺激和预期反应的情况下,它很有用
cleanup释放特性方法使用的任何资源

一个cleanup块后面只能跟一个where块,不能重复。与cleanup方法一样,它用于释放特性方法使用的任何资源,即使特性方法(的前一部分)产生了异常,它也会运行。因此,cleanup必须对块进行防御性编码;在最坏的情况下,它必须优雅地处理特征方法中的第一条语句抛出异常的情况,并且所有局部变量仍然具有其默认值。

对象级规范通常不需要cleanup方法,因为它们消耗的唯一资源是内存,垃圾收集器会自动回收内存。然而,更粗粒度的规范可能使用cleanup 块来清理文件系统、关闭数据库连接或关闭网络服务。

where使用不同的输入和预期结果多次执行相同的测试代码,主要用于数据驱动的测试

1.4With与VerifyAll

def "offered PC matches preferred configuration"() {
  when:
  def pc = shop.buyPc()

  then:
  with(pc) {
    vendor == "Sunny"
    clockRate >= 2333
    ram >= 406
    os == "Linux"
  }
}

      您可以使用一种with(target, closure)方法与正在验证的对象进行交互,当pc对象为null时会抛出异常。这在thenexpect块中特别有用。

正常期望在第一个失败的断言上无法通过测试。有时在测试失败之前收集这些失败以获得更多信息是有帮助的,这种行为也称为软断言。verifyAll方法可以像这样使用with

def "offered PC matches preferred configuration"() {
  when:
  def pc = shop.buyPc()

  then:
  verifyAll(pc) {
    vendor == "Sunny"
    clockRate >= 2333
    ram >= 406
    os == "Linux"
  }
}

二、Mock

在我们测试的过程中很多资源是无法获取或者说无法直接使用的,比如我们调用一个三方的接口,其实我们是无法确认对方的返回内容永不变动的,但是这种不稳定因素就会对我们的测试结果产生影响,所以就需要我们自己去模拟一些对象方法调用或接口的返回,Mock就此登场。就我个人使用而言感觉Mock与@MockBean的作用是相似的。

class PublisherSpec extends Specification {
    //模拟接口
    def testMock=Mock(TestMock.class)
    def room=new Room(testMock:testMock)

    def "test mock"(){

        given:"initData"
        def name="张三"

        and:"mock"
        //模拟接口调用的返回
        testMock.getName()>>name

        when:"exec"
        def result=room.get(1)

        then:"assert"
        assert result=="张三"
    }


}

测试相关类及接口定义:

public class Room {

    public TestMock testMock;

    public String get(Integer index){
        return testMock.getName();
    }

}
public interface TestMock {

    String getName();

}

        在这里尽管TestMock接口并没有对应的实现类,但是我们还是可以使用该类的方法, 这是因为与大多数 Java模拟框架一样,Spock 使用 JDK 动态代理(模拟接口时)和Byte BuddyCGLIB代理(模拟类时)在运行时生成模拟实现。

       与 Mockito 一样,我们坚信模拟框架默认应该是宽松的。这意味着对模拟对象的意外方法调用(或者,换句话说,与手头测试无关的交互)被允许并以默认响应回答。相反,像 EasyMock 和 JMock 这样的模拟框架在默认情况下是严格的,并且会为每个意外的方法调用抛出异常。虽然严格强制严格,但它也可能导致过度规范,导致脆弱的测试在每次其他内部代码更改时失败。Spock 的模拟框架可以轻松地仅描述与交互相关的内容,避免过度规范的陷阱。

三、Mock,stub,spy

stub

stub只是简单的生成一个目标类的代理类,关注重点为方法的返回,对于方法执行的次数等不关注。

def venderWorkOrderCmdRpc = Stub(VenderWorkOrderCmdRpc)

Mock

在stub的基础上有了方法执行次数的关注

given:
subscriber.receive("message1") >> "ok"

when:
publisher.send("message1")

then:
1 * subscriber.receive("message1")  //标识该方法应该只执行一次  ,0*标识一次也不执行

如果stub的对象添加方法执行次数的判断会抛出InvalidSpecException的异常。

spy

spy总是基于真实的对象。因此,必须提供类类型而不是接口类型,以及该类型的任何构造函数参数。如果未提供构造函数参数,则将使用该类型的无参数构造函数。

WorkOrderCreateCheckAbilityImpl workOrderCreateCheckAbility = Spy()

四、参考文献

Spock美团技术实践总结:Spock单元测试框架介绍以及在美团优选的实践 - 美团技术团队

Spock单元测试框架保姆级教程:https://javakk.com/category/spock/page/2

Spock官方文档:Spock Framework Reference Documentation

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GroovySpock是一对非常强大的测试工具,经常被用于Java应用程序的单元测试和集成测试Groovy是一种动态语言,它能够与Java无缝集成,提供了更简洁、灵活的语法和更强大的功能。Spock是一种基于Groovy测试框架,它结合了传统的单元测试框架和行为驱动开发(BDD)的思想,提供了一种更易读、更易维护的测试编写方式。 使用GroovySpock进行测试非常简单。首先,你需要在项目中引入相应的依赖。对于Groovy,你可以在项目的构建工具(如Maven或Gradle)中添加Groovy的依赖。对于Spock,你需要添加spock-core和spock-spring(如果需要与Spring集成)这两个依赖。 在编写测试时,你可以使用Spock提供的各种注解和断言来编写测试逻辑。Spock的语法非常接近自然语言,能够更好地表达测试的意图。以下是一个简单的示例: ```groovy import spock.lang.Specification class MySpec extends Specification { def "test addition"() { given: def a = 2 def b = 3 when: def result = a + b then: result == 5 } } ``` 在这个示例中,我们定义了一个名为"test addition"的测试方法。在given块中,我们初始化了两个变量a和b。在when块中,我们执行了相加操作并将结果赋给result变量。在then块中,我们使用断言来验证结果是否等于5。 你可以使用任何GroovyJava的特性来编写测试逻辑,包括使用Mockito等库进行模拟和依赖注入。 希望这能帮助你入门GroovySpock测试!如果你有更多的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值