Groovy+Spock單元測試


一、導入依賴

Spock是基於JUnit的單測框架,提供一些更好的語法,結合Groovy語言,可以寫出更為簡潔的單測。

<!-- groovy依賴 -->
<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>2.4.0</version>
</dependency>
<!-- spock核心依賴 -->
<dependency>
    <groupId>org.spockframework</groupId>
    <artifactId>spock-core</artifactId>
    <version>1.3-groovy-2.4</version>
    <scope>test</scope>
</dependency>
<!-- spring spock依賴 -->
<dependency>
    <groupId>org.spockframework</groupId>
    <artifactId>spock-spring</artifactId>
    <version>1.3-groovy-2.4</version>
    <scope>test</scope>
</dependency>
<!-- 單元測試依賴 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.1</version>
    <scope>test</scope>
</dependency>

二、測試例子

繼承Specification

package com.qiang.groovy.controller

import com.qiang.groovy.controller.ConfigInfoController
import spock.lang.Specification

class ConfigInfoControllerGroovy extends Specification {
    
}

固定方法

/**
 * 在第一個測試方法開始前執行一遍
 */
def setupSpec() {
    println "------------ setupSpec()方法 ------------"
}

/**
 * 每個測試方法開始前都會執行一遍
 */
def setup() {
    println "------------ setup()方法 ------------"
}

/**
 * 每個測試方法后都會執行一遍
 */
def cleanup() {
    println "------------ cleanup()方法 ------------"
}

/**
 * 最后一個測試方法后執行
 */
def cleanupSpec() {
    println "------------ cleanupSpec()方法 ------------"
}

測試例子

def "測試a>b"() {
  given:
    def a = new Random().nextInt(10)
    def b = 2
  expect:
    println a
    a > b
}

點擊運行

image-20210415004728353

測試通過

image-20210415004802675

測試不通過

image-20210415004841948

三、基本構造

  • where: 以表格的形式提供測試數據集合
  • when: 觸發行為,比如調用指定方法或函數
  • then: 做出斷言表達式
  • expect: 期望的行為,when-then的精簡版
  • given: mock單測中指定mock數據
  • thrown: 如果在when方法中拋出了異常,則在這個子句中會捕獲到異常並返回
  • def setup() {} :每個測試運行前的啟動方法
  • def cleanup() {} : 每個測試運行后的清理方法
  • def setupSpec() {} : 第一個測試運行前的啟動方法
  • def cleanupSpec() {} : 最后一個測試運行后的清理方法

四、構造例子

4.1 expect-where

在where子句中以表格形式給出一系列輸入輸出的值,然后在expect中引用。

@Unroll
def "測試expect-where"() {
    expect:
    	userInfoService.getById(id).getName() == name
    where:
    	id | name
        1  | "小強"
        2  | "傻狗"
        3  | "小豬"
}

4.2 given-when-then

當條件滿足時,達到期望的結果。

@Unroll
@Transactional
def "測試given-when-then"() {
    given:
    // 數據准備
    UserInfo userInfo = new UserInfo()
    userInfo.setName("小強崽")
    userInfo.setAsset(10000000.00)
    when:
    // 條件判斷
    boolean flag = userInfoService.save(userInfo)
    then:
    // 期望結果
    flag
}

4.3 when-then-thrown

測試異常信息。

/**
* 模擬異常
*/
@Override
public void getExceptionMessage() {
    throw new RuntimeException("模擬異常");
}

測試方法。

def "測試異常thrown"() {
    when:
    // 此方法會拋出RuntimeException
    userInfoService.getExceptionMessage()
    then:
    // 接收異常
    def ex = thrown(Exception)
    ex.class.name == "java.lang.RuntimeException"
    ex.getMessage() == "模擬異常"
}

五、遠程擋板

遠程調用第三方服務需要Mock擋板,避免受第三方服務的影響。

遠程調用服務

package com.qiang.groovy.controller;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qiang.common.response.ResponseResult;
import com.qiang.groovy.entity.UserInfo;
import com.qiang.groovy.feign.GiteeServiceFeign;
import com.qiang.groovy.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.io.Serializable;
import java.util.List;

/**
 * @author 小強崽
 * @create: 2021-04-11 21:31:14
 * @description: 控制層
 */
@RestController
@RequestMapping("/user/info")
public class UserInfoController {
	/**
     * 注入遠程調用接口
     */
    @Autowired
    private GiteeServiceFeign giteeServiceFeign;

    /**
     * 遠程調用
     *
     * @param msg
     * @return
     */
    @GetMapping("/gitee/test/feign")
    public ResponseResult<String> testFeign(@RequestParam("msg") String msg) {
        return giteeServiceFeign.testFeign(msg);
    }
}

遠程調用做擋板后,自定義擋板返回的參數即可。

package com.qiang.groovy.controller

import com.qiang.common.response.ResponseResult
import com.qiang.common.util.SpringContextUtil
import com.qiang.groovy.feign.GiteeServiceFeign
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification

@SpringBootTest
class UserInfoControllerGroovy extends Specification {

    @Autowired
    private UserInfoController userInfoController

    @Autowired
    private SpringContextUtil springContextUtil;

    /**
     * 單元測試調用第三方服務時,需要做擋板
     */
    def giteeServiceFeign = Mock(GiteeServiceFeign)

    def "測試遠程調用"() {
        given:
        // 定義擋板返回的參數,(*_)為任意入參參數
        giteeServiceFeign.testFeign(*_) >> ResponseResult.success()
        expect:
        userInfoController.testFeign(msg).code == result
        where:
        msg   | result
        "msg" | "200"
    }

    /**
     * 每個測試方法開始前都會執行一遍
     */
    def setup() {
        // 擋板賦值
        userInfoController.giteeServiceFeign = giteeServiceFeign
    }

    /**
     * 每個測試方法后都會執行一遍
     */
    def cleanup() {
        // 還原擋板
        userInfoController.giteeServiceFeign = springContextUtil.getBean(GiteeServiceFeign.class)
    }

}

六、常用注解

6.1 @Unroll

某個測試用例失敗了,卻難以查到是哪個失敗了,這時候,可以使用@Unroll注解,該注解會將where子句的每個測試用例轉化為一個 @Test 獨立測試方法來執行,這樣就很容易找到錯誤的用例。

@Unroll
def "測試getById()"() {
    expect:
    	userInfoService.getById(id).getName() == name
    where:
    	id | name
        1  | "小強"
        2  | "傻狗"
        3  | "小豬"
}

沒加@Unroll之前

image-20210421172526968

加了@Unroll之后

image-20210421172739758

6.2 @Transactional

插入數據時,加上該注解測試完成會回滾數據。

image-20210421180516967

作者(Author):小強崽
來源(Source):https://www.wuduoqiang.com/archives/Groovy+Spock單元測試
協議(License):署名-非商業性使用-相同方式共享 4.0 國際 (CC BY-NC-SA 4.0)
版權(Copyright):商業轉載請聯系作者獲得授權,非商業轉載請注明出處。 For commercial use, please contact the author for authorization. For non-commercial use, please indicate the source.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM