
0. 為什么人人都討厭寫單測
在之前的關(guān)于swagger文章里提到過,程序員最討厭的兩件事,一件是別人不寫文檔,另一件就是自己寫文檔。這里如果把文檔換成單元測試也同樣成立。
每個開發(fā)人員都明白單元測試的作用,也都知道代碼覆蓋率越高越好。高覆蓋率的代碼,相對來說出現(xiàn) BUG 的概率就越低,在線上運行就越穩(wěn)定,接的鍋也就越少,就也不會害怕測試同事突然的關(guān)心。
既然這么多好處,為什么還會討厭他呢?至少在我看來,單測有如下幾點讓我喜歡不起來的理由。
第一,要額外寫很多很多的代碼,一個高覆蓋率的單測代碼,往往比你要測試的,真正開發(fā)的業(yè)務(wù)代碼要多,甚至是業(yè)務(wù)代碼的好幾倍。這讓人覺得難以接受,你想想開發(fā) 5 分鐘,單測 2 小時是什么樣的心情。而且并不是單測寫完就沒事了,后面業(yè)務(wù)要是變更了,你所寫的單測代碼也要同步維護。
第二,即使你有那個耐心去寫單測,但是在當前這個拼速度擠時間的大環(huán)境下,會給你那么多寫單測的時間嗎?寫一個單測的時間可以實現(xiàn)一個需求,你會如何去選?
第三,寫單測通常是一件很無趣的事,因為他比較死,主要目的就是為了驗證,相比之下他更像是個體力活,沒有真正寫業(yè)務(wù)代碼那種創(chuàng)造的成就感。寫出來,驗證不出bug很失落,白寫了,驗證出bug又感到自己是在打自己臉。
1. 為什么人人又必須寫單測
所以得到的結(jié)論就是不寫單測?那么問題又來了,出來混遲早是要還的,上線出了問題,最終責任人是誰?不是提需求的產(chǎn)品、不是沒發(fā)現(xiàn)問題的測試同學,他們頂多就是連帶責任。最該負責的肯定是寫這段代碼的你。特別是對于那些從事金融、交易、電商等息息相關(guān)業(yè)務(wù)的開發(fā)人員,跟每行代碼打交通的都是真金白銀。每次明星搞事,微博就掛,已經(jīng)被傳為笑談,畢竟只是娛樂相關(guān),如果掛的是支付寶、微信,那用戶就沒有那么大的包容度了。這些業(yè)務(wù)如果出現(xiàn)嚴重問題,輕則掃地出門,然后整個職業(yè)生涯背負這個污點,重則直接從面向?qū)ο箝_發(fā)變成面向監(jiān)獄開發(fā)。所以單元測試保護的不僅僅是程序,更保護的是寫程序的你。
最后得出了一個無可奈何的結(jié)論,單測是個讓人又愛又恨的東西,是不想做但又不得不做的事情。雖然我們沒辦法改變要寫單測這件事,但是我們可以改變怎么去寫單元測試這件事。
2. SPOCK 可以幫你改善單測體驗
當然,本文不是教你用旁門左道的方法提高代碼覆蓋率。而是通過一個神奇的框架 spock 去提高你編寫單元測試的效率。spock 這名稱來源,個人猜測是因為《星際迷航》的同名人物(封面圖)。那么spock 是如何提高編寫單測的效率呢?我覺得有以下幾點:
第一,他可以用更少的代碼去實現(xiàn)單元測試,讓你可以更加專注于去驗證結(jié)果而不是寫單測代碼的過程。那么他又是如何做到少寫代碼這件事呢?原來他使用一種叫做 groovy 的魔法。
groovy 其實是一門基于 jvm 的動態(tài)語言??梢院唵蔚睦斫獬膳茉?jvm 上的 python 或 js。說到這里,可能沒有接觸過動態(tài)語言的同學,對它們都會有一個比較刻板的印象,太過于靈活,很容易出現(xiàn)問題,且可維護性差,所以有了那一句『動態(tài)一時爽,全家 xxx』的梗。首先,這些的確是他的問題,嚴格的說是使用不當時才帶來的問題。所以主要還是看使用的人。比如安卓領(lǐng)域的官方依賴管理工具 gradle 就是基于 groovy 開發(fā)的。
另外不要誤以為我學這門框架,還要多學一門語言,成本太大。其實大可不必擔心,你如果會 groovy 當然更好,如果不會也沒有關(guān)系。因為 groovy 是基于 java 的,所以完全可以放心大膽的使用 java 的語法,某些要用到的 groovy 獨有的語法很少,而且后面都會告訴你。
第二,他有更好的語義化,讓你的單測代碼可讀性更高。
語義化這個詞可能不太好理解。舉兩個例子來說吧,第一個是語義化比較好的語言 -- HTML。他的語法特點就是標簽,不同的類型放在不同的標簽里。比如 head 就是頭部的信息,body 是主體內(nèi)容的信息,table 就是表格的信息,對于沒有編程經(jīng)驗的人來說,也可以很容易理解。第二個是語義化比較差的語言 -- 正則。他可以說基本上沒有語義這種東西,由此導致的直接問題就是,即使是你自己的寫的正則,幾天之后你都不知道當時寫的是什么。比如下面這個正則,你能猜出他是什么意思嗎?(可以留言回復(fù))
((?:(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d?\d))
3. 領(lǐng)略 SPOCK 的魔法
3.1 引入依賴
<!--如果沒有使得 spring boot,以下包可以省略-->
<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>1.3-groovy-2.5</version>
<scope>test</scope>
</dependency>
<!--引入spock 與 spring 集成包-->
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.3-groovy-2.5</version>
<scope>test</scope>
</dependency>
<!--引入 groovy 依賴-->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.5.7</version>
<scope>test</scope>
</dependency>
說明
注釋已經(jīng)標明,第一個包是 spring boot 項目需要使用的,如果你只是想使用 spock,只要最下面 3 個即可。其中第一個包 spock-core 提供了 spock 的核心功能,第二個包 spock-spring 提供了與 spring 的集成(不用 spring 的情況下也可以不引入)。 注意這兩個包的版本號 -> 1.3-groovy-2.5。第一個版本號 1.3 其實代表是 spock 的版本,第二個版本號代表的是 spock 所要依賴的 groovy 環(huán)境的版本。最后一個包就是我們要依賴的 groovy 。
3.2 準備基礎(chǔ)測試類
3.2.1 Calculator.java
/*
* *
* * blog.coder4j.cn
* * Copyright (C) 2016-2019 All Rights Reserved.
*
*/
package cn.coder4j.study.example.spock;
/**
* @author buhao
* @version Calculator.java, v 0.1 2019-10-30 10:34 buhao
*/
public class Calculator {
/**
* 加操作
*
* @param num1
* @param num2
* @return
*/
public static int add(int num1, int num2) {
return num1 + num2;
}
/**
* 整型除操作
*
* @param num1
* @param num2
* @return
*/
public static int divideInt(int num1, int num2) {
return num1 / num2;
}
/**
* 浮點型操作
* @param num1
* @param num2
* @return
*/
public static double divideDouble(double num1, double num2){
return num1 / num2;
}
}
說明
這是一個很簡單的計算器類。只寫了三個方法,一個是加法的操作、一個整型的除法操作、一個浮點類型的除法操作。
3.3 開始單測 Calculator.java
3.3.1 創(chuàng)建單測類 CalculatorTest.groovy
class CalculatorTest extends Specification {
}
說明
這里一定要注意,之前我們已經(jīng)說了 spock 是基于 groovy 。所以單測類的后綴不是 .java 而** .groovy**。千萬不要創(chuàng)建成普通 java 類了。否則創(chuàng)建沒有問題,但是寫一些 groovy 語法會報錯。如果你用的是 IDEA 可以通過如下方式創(chuàng)建,以前創(chuàng)建 Java 類我們都是選擇第一個選項,現(xiàn)在我們選擇第三個 Groovy Class 就可以了。

另外就是 spock 的測試類需要繼承 **spock.lang.Specification **類。
3.3.2 驗證加操作 - expect
def "test add"(){
expect:
Calculator.add(1, 1) == 2
}
說明
def 是 groovy 的關(guān)鍵字,可以用來定義變量跟方法名。后面 "test add" 是你單元測試的名稱,也可以用中文。最后重點說明的是 expect 這個關(guān)鍵字。
expect 字面上的意思是期望,我們期望什么樣的事情發(fā)生。在使用其它單測框架時,與之類似的是 assert 。比如 _Assert.assertEquals(_Calculator.add(_1 + 1), 2) _這樣,表示我們斷言加操作傳入1 與 1 相加結(jié)果為 2。如果結(jié)果是這樣則用例通過,如果不是則用例失敗。這與我們上面的代碼功能上完成一致。
expect 的語法意義就是在 expect 的塊內(nèi),所有表達式成立則驗證通過,反之有任一個不成立則驗證失敗。這里引入了一個塊的概念。怎么理解 spock 的塊呢?我們上面說 spock 有良好的語義化及更好的閱讀性就是因為這個塊的作用。可以類比成 html 中的標簽。html 的標簽的范圍是兩個標簽之間,而 spock 更簡潔一點,從這個標簽開始到下一個標簽開始或代碼結(jié)束的地方,就是他的范圍。我們只要看到 expect 這個標簽就明白,他的范圍內(nèi)都是我們預(yù)期要得到的結(jié)果。
3.3.3 驗證加操作 - given - and
這里代碼比較簡單,參數(shù)我只使用了一次,所以直接寫死。如果想復(fù)用,我就得把這些參數(shù)抽成變量。這個時候可以使用 spock 的 given 塊。given 的語法意義相當于是一個初始化的代碼塊。
def "test add with given"(){
given:
def num1 = 1
def num2 = 1
def result = 2
expect:
Calculator.add(num1, num2) == result
}
當然你也可以像下面這樣寫,但是嚴重不推薦,因為雖然可以達到同樣的效果,但是不符合 spock 的語義。就像我們一般是在 head 里面引入 js、css,但是你在 body 或者任何標簽里都可以引入,語法沒有問題但是破壞了語義,不便理解與維護。
// 反倒
def "test add with given"(){
expect:
def num1 = 1
def num2 = 1
def result = 2
Calculator.add(num1, num2) == result
}
如果你還想讓語義更好一點,我們可以把參數(shù)與結(jié)果分開定義,這個時候可以使用 and 塊。它的語法功能可以理解成同他上面最近的一個標簽。
def "test add with given and"(){
given:
def num1 = 1
def num2 = 1
and:
def result = 2
expect:
Calculator.add(num1, num2) == result
}
3.3.4 驗證加操作 - expect - where
看了上面例子,可能覺得 spock 只是語義比較好,但是沒有少寫幾行代碼呀。別急,下面我們就來看 spock 的一大殺器 where。
def "test add with expect where"(){
expect:
Calculator.add(num1, num2) == result
where:
num1 | num2 | result
1 | 1 | 2
1 | 2 | 3
1 | 3 | 4
}
where 塊可以理解成準備測試數(shù)據(jù)的地方,他可以跟 expect 組合使用。上面代碼里 expect 塊里面定義了三個變量 num1、num2、result。這些數(shù)據(jù)我們可以在 where 塊里定義。where 塊使用了一種很像 markdown 中表格的定義方法。第一行或者說表頭,列出了我們要傳數(shù)據(jù)的變量名稱,這里要與 expect 中對應(yīng),不能少但是可以多。其它行都是數(shù)據(jù)行,與表頭一樣都是通過 『 | 』 號分隔。通過這樣,spock 就會跑 3 次用例,分別是 1 + 2 = 2、1 + 2 = 3、1 + 3 = 4 這些用例。怎么樣?是不是很方便,后面再擴充用例只要再加一行數(shù)據(jù)就可以了。
3.3.5 驗證加操作 - expect - where - @Unroll
上面這些用例都是正常可以跑通的,如果是 IDEA 跑完之后會如下所示:

那么現(xiàn)在我們看看如果有用例不通過會怎么樣,把上面代碼的最后一個 4 改成 5
def "test add with expect where"(){
expect:
Calculator.add(num1, num2) == result
where:
num1 | num2 | result
1 | 1 | 2
1 | 2 | 3
1 | 3 | 5
}
再跑一次,IDEA 會出現(xiàn)如下顯示

左邊標注出來的是用例執(zhí)行結(jié)果,可以看出來雖然有 3 條數(shù)據(jù),其中 2 條數(shù)據(jù)是成功,但是只會顯示整體的成功與否,所以顯示未通過。但是 3 條數(shù)據(jù),我怎么知道哪條沒通過呢?
右邊標注出來的是 spock 打印的的錯誤日志??梢院芮宄目吹?,在 num1 為 1,num2 為 3,result 為 5 并且 他們之間的判斷關(guān)系為 == 的結(jié)果是 false 才是正確的。 spock 的這個日志打印的是相當歷害,如果是比較字符串,還會計算異常字符串與正確字符串之間的匹配度,有興趣的同學,可以自行測試。
嗯,雖然可以通過日志知道哪個用例沒通過,但是還是覺得有點麻煩。spock 也知道這一點。所以他還同時提供了一個** @Unroll **注解。我們在上面的代碼上再加上這個注解:
@Unroll
def "test add with expect where unroll"(){
expect:
Calculator.add(num1, num2) == result
where:
num1 | num2 | result
1 | 1 | 2
1 | 2 | 3
1 | 3 | 5
}
運行結(jié)果如下: 
通過添加** @Unroll** 注解,spock 自動把上面的代碼拆分成了 3 個獨立的單測測試,分別運行,運行結(jié)果更清晰了。
那么還能更清晰嗎?當然可以,我們發(fā)現(xiàn) spock 拆分后,每個用例的名稱其實都是你寫的單測方法的名稱,然后后面加一個數(shù)組下標,不是很直觀。我們可以通過 groovy 的字符串語法,把變量放入用例名稱中,代碼如下:
@Unroll
def "test add with expect where unroll by #num1 + #num2 = #result"(){
expect:
Calculator.add(num1, num2) == result
where:
num1 | num2 | result
1 | 1 | 2
1 | 2 | 3
1 | 3 | 5
}
如上,我們在方法名后加了一句 #num1 + #num2 = #result。這里有點類似我們在 mybatis 或者一些模板引擎中使用的方法。# 號拼接聲明的變量就可以了,執(zhí)行后結(jié)果如下。

這下更清晰了。
另外一點,就是 where 默認使用的是表格的這種形式:
where:
num1 | num2 | result
1 | 1 | 2
1 | 2 | 3
1 | 3 | 5
很直觀,但是這種形式有一個弊端。上面 『 | 』 號對的這么整齊。都是我一個空格一個 TAG 按出來的。雖然語法不要求對齊,但是逼死強迫癥。不過,好在還可以有另一種形式:
@Unroll
def "test add with expect where unroll arr by #num1 + #num2 = #result"(){
expect:
Calculator.add(num1, num2) == result
where:
num1 << [1, 1, 2]
num2 << [1, 2, 3]
result << [1, 3, 4]
}
可以通過 『<<』 符(注意方向),把一個數(shù)組賦給變量,等同于上面的數(shù)據(jù)表格,沒有表格直觀,但是比較簡潔也不用考慮對齊問題,這兩種形式看個人喜好了。
3.3.6 驗證整數(shù)除操作 - when - then
我們都知道一個整數(shù)除以0 會有拋出一個『/ by zero』異常,那么如果斷言這個異常呢。用上面 expect 不太好操作,我們可以使用另一個類似的塊** when ... then**。
@Unroll
def "test int divide zero exception"(){
when:
Calculator.divideInt(1, 0)
then:
def ex = thrown(ArithmeticException)
ex.message == "/ by zero"
}
**when ... then **通常是成對出現(xiàn)的,它代表著當執(zhí)行了 when 塊中的操作,會出現(xiàn) then 塊中的期望。比如上面的代碼說明了,當執(zhí)行了 _Calculator.divideInt(1, 0) _的操作,就一定會拋出 ArithmeticException 異常,并且異常信息是 / by zero。
3.4 準備Spring測試類
上面我們已經(jīng)學會了 spock 的基礎(chǔ)用法,下面我們將學習與 spring 整合的知識,首先創(chuàng)建幾個用于測試的demo 類
3.4.1 User.java
/*
* *
* * blog.coder4j.cn
* * Copyright (C) 2016-2019 All Rights Reserved.
*
*/
package cn.coder4j.study.example.spock.model;
import java.util.Objects;
/**
* @author buhao
* @version User.java, v 0.1 2019-10-30 16:23 buhao
*/
public class User {
private String name;
private Integer age;
private String passwd;
public User(String name, Integer age, String passwd) {
this.name = name;
this.age = age;
this.passwd = passwd;
}
/**
* Getter method for property <tt>passwd</tt>.
*
* @return property value of passwd
*/
public String getPasswd() {
return passwd;
}
/**
* Setter method for property <tt>passwd</tt>.
*
* @param passwd value to be assigned to property passwd
*/
public void setPasswd(String passwd) {
this.passwd = passwd;
}
/**
* Getter method for property <tt>name</tt>.
*
* @return property value of name
*/
public String getName() {
return name;
}
/**
* Setter method for property <tt>name</tt>.
*
* @param name value to be assigned to property name
*/
public void setName(String name) {
this.name = name;
}
/**
* Getter method for property <tt>age</tt>.
*
* @return property value of age
*/
public Integer getAge() {
return age;
}
/**
* Setter method for property <tt>age</tt>.
*
* @param age value to be assigned to property age
*/
public void setAge(Integer age) {
this.age = age;
}
public User() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(name, user.name) &&
Objects.equals(age, user.age) &&
Objects.equals(passwd, user.passwd);
}
@Override
public int hashCode() {
return Objects.hash(name, age, passwd);
}
}
3.4.2 UserDao.java
/*
* *
* * blog.coder4j.cn
* * Copyright (C) 2016-2019 All Rights Reserved.
*
*/
package cn.coder4j.study.example.spock.dao;
import cn.coder4j.study.example.spock.model.User;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* @author buhao
* @version UserDao.java, v 0.1 2019-10-30 16:24 buhao
*/
@Component
public class UserDao {
/**
* 模擬數(shù)據(jù)庫
*/
private static Map<String, User> userMap = new HashMap<>();
static {
userMap.put("k",new User("k", 1, "123"));
userMap.put("i",new User("i", 2, "456"));
userMap.put("w",new User("w", 3, "789"));
}
/**
* 通過用戶名查詢用戶
* @param name
* @return
*/
public User findByName(String name){
return userMap.get(name);
}
}
3.4.3 UserService.java
/*
* *
* * blog.coder4j.cn
* * Copyright (C) 2016-2019 All Rights Reserved.
*
*/
package cn.coder4j.study.example.spock.service;
import cn.coder4j.study.example.spock.dao.UserDao;
import cn.coder4j.study.example.spock.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author buhao
* @version UserService.java, v 0.1 2019-10-30 16:29 buhao
*/
@Service
public class UserService {
@Autowired
private UserDao userDao;
public User findByName(String name){
return userDao.findByName(name);
}
public void loginAfter(){
System.out.println("登錄成功");
}
public void login(String name, String passwd){
User user = findByName(name);
if (user == null){
throw new RuntimeException(name + "不存在");
}
if (!user.getPasswd().equals(passwd)){
throw new RuntimeException(name + "密碼輸入錯誤");
}
loginAfter();
}
}
3.4.3 Application.java
/*
* *
* * blog.coder4j.cn
* * Copyright (C) 2016-2019 All Rights Reserved.
*
*/
package cn.coder4j.study.example.spock;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.5 與 spring 集成測試
/*
* *
* * blog.coder4j.cn
* * Copyright (C) 2016-2019 All Rights Reserved.
*
*/
package cn.coder4j.study.example.spock.service
import cn.coder4j.study.example.spock.model.User
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification
import spock.lang.Unroll
@SpringBootTest
class UserServiceFunctionTest extends Specification {
@Autowired
UserService userService
@Unroll
def "test findByName with input #name return #result"() {
expect:
userService.findByName(name) == result
where:
name << ["k", "i", "kk"]
result << [new User("k", 1, "123"), new User("i", 2, "456"), null]
}
@Unroll
def "test login with input #name and #passwd throw #errMsg"() {
when:
userService.login(name, passwd)
then:
def e = thrown(Exception)
e.message == errMsg
where:
name | passwd | errMsg
"kd" | "1" | "${name}不存在"
"k" | "1" | "${name}密碼輸入錯誤"
}
}
spock 與 spring 集成特別的簡單,只要你加入了開頭所說的 spock-spring 和 spring-boot-starter-test。再于測試代碼的類上加上 @SpringBootTest 注解就可以了。想用的類直接注入進來就可以了,但是要注意的是這里只能算功能測試或集成測試,因為在跑用例時是會啟動 spring 容器的,外部依賴也必須有。很耗時,而且有時候外部依賴本地也跑不了,所以我們通常都是通過 mock 來完成單元測試。
3.6 與 spring mock 測試
/*
* *
* * blog.coder4j.cn
* * Copyright (C) 2016-2019 All Rights Reserved.
*
*/
package cn.coder4j.study.example.spock.service
import cn.coder4j.study.example.spock.dao.UserDao
import cn.coder4j.study.example.spock.model.User
import spock.lang.Specification
import spock.lang.Unroll
class UserServiceUnitTest extends Specification {
UserService userService = new UserService()
UserDao userDao = Mock(UserDao)
def setup(){
userService.userDao = userDao
}
def "test login with success"(){
when:
userService.login("k", "p")
then:
1 * userDao.findByName("k") >> new User("k", 12,"p")
}
def "test login with error"(){
given:
def name = "k"
def passwd = "p"
when:
userService.login(name, passwd)
then:
1 * userDao.findByName(name) >> null
then:
def e = thrown(RuntimeException)
e.message == "${name}不存在"
}
@Unroll
def "test login with "(){
when:
userService.login(name, passwd)
then:
userDao.findByName("k") >> null
userDao.findByName("k1") >> new User("k1", 12, "p")
then:
def e = thrown(RuntimeException)
e.message == errMsg
where:
name | passwd | errMsg
"k" | "k" | "${name}不存在"
"k1" | "p1" | "${name}密碼輸入錯誤"
}
}
spock 使用 mock 也很簡單,直接使用 Mock(類) 就可以了。如上代碼 UserDao userDao = Mock(UserDao) 。上面寫的例子中有幾點要說明一下,以如下這個方法為例:
def "test login with error"(){
given:
def name = "k"
def passwd = "p"
when:
userService.login(name, passwd)
then:
1 * userDao.findByName(name) >> null
then:
def e = thrown(RuntimeException)
e.message == "${name}不存在"
}
given、when、then 不用說了,大家已經(jīng)很熟悉了,但是第一個 then 里面的 **1 * userDao.findByName(name) >> null **是什么鬼?
首先,我們可以知道的是,一個用例中可以有多個 then 塊,對于多個期望可以分別放在多個 then 中。
第二, 1 * xx 表示 期望 xx 操作執(zhí)行了 1 次。1 * userDao.findByName(name) 就表現(xiàn)當執(zhí)行 _userService.login(name, passwd) 時我期望執(zhí)行 1 次 userDao.findByName(name) _方法。如果期望不執(zhí)行這個方法就是0 * xx,這在條件代碼的驗證中很有用,然后 >> null 又是什么意思?他代表當執(zhí)行了 userDao.findByName(name) 方法后,我讓他結(jié)果返回 null。因為 userDao 這個對象是我們 mock 出來的,他就是一個假對象,為了讓后續(xù)流程按我們的想法進行,我可以通過『 >>』 讓 spock 模擬返回指定數(shù)據(jù)。
第三,要注意第二個 then 代碼塊使用 ${name} 引用變量,跟標題的 #name 是不同的。
3.7 其它內(nèi)容
3.7.1 公共方法
| 方法名 | 作用 |
|---|---|
| setup() | 每個方法執(zhí)行前調(diào)用 |
| cleanup() | 每個方法執(zhí)行后調(diào)用 |
| setupSpec() | 每個方法類加載前調(diào)用一次 |
| cleanupSpec() | 每個方法類執(zhí)行完調(diào)用一次 |
這些方法通常用于測試開始前的一些初始化操作,和測試完成后的清理操作,如下:
def setup() {
println "方法開始前初始化"
}
def cleanup() {
println "方法執(zhí)行完清理"
}
def setupSpec() {
println "類加載前開始前初始化"
}
def cleanupSpec() {
println "所以方法執(zhí)行完清理"
}
3.7.2 @Timeout
對于某些方法,需要規(guī)定他的時間,如果運行時間超過了指定時間就算失敗,這時可以使用 timeout 注解
@Timeout(value = 900, unit = TimeUnit.MILLISECONDS)
def "test timeout"(){
expect:
Thread.sleep(1000)
1 == 1
}
注解有兩個值,一個是 value 我們設(shè)置的數(shù)值,unit 是數(shù)值的單位。
3.7.3 with
def "test findByName by verity"() {
given:
def userDao = Mock(UserDao)
when:
userDao.findByName("kk") >> new User("kk", 12, "33")
then:
def user = userDao.findByName("kk")
with(user) {
name == "kk"
age == 12
passwd == "33"
}
}
with 算是一個語法糖,沒有他之前我們要判斷對象的值只能,user.getXxx() == xx。如果屬性過多也是挺麻煩的,用 with 包裹之后,只要在花括號內(nèi)直接寫屬性名稱即可,如上代碼所示。
4. 其它
4.1 完整代碼
因為篇幅有限,無法貼完所有代碼,完整代碼已上傳 github。
4.2 參考文檔
本文在瞻仰了如下博主的精彩博文后,再加上自身的學習總結(jié)加工而來,如果本文在看的時候有不明白的地方可以看一下下方鏈接。