Spock套件基於一個單元測試框架,它有比junit更爲簡潔高效的測試語法。html
Spock中一個單元測試類名叫Specification。全部的單元測試類,都須要繼承Specificationjava
class MyFirstSpecification extends Specification { // fields // fixture methods // feature methods // helper methods }
對於spock來講,Specification表明了一個軟件、應用、類的使用規範,其中的全部單元測試方法,被稱爲feature,即功能。sql
一個feature method的執行邏輯大概是以下幾步:數據庫
因此,對spock來講,一個單元測試,實際上是這個軟件應用提供的功能使用規範,這個規範中提供了每一個功能的使用說明書,輸入什麼,會獲得什麼,大致是按這個見解,去寫單元測試的。express
就像junit同樣,咱們能夠對整個單元測試類作一些前置,並清理。也能夠對每一個單元測試的方法作一些前置後清理。框架
其跟Junit的類比關係爲dom
setupSpec 對應 @BeforeClass setup 對應 @Before cleanup 對應 @After cleanupSpec 對應 @AfterClass
同時因爲Spock的單元測試自己是會集成Specification 父類的,因此父類中的前置、後置方法也會被調用,不過不用顯示調用,會自動調用。性能
一個測試功能方法執行時,其總體的執行順序爲:單元測試
super.setupSpec sub.setupSpec super.setup sub.setup **feature method sub.cleanup super.cleanup sub.cleanupSpec super.cleanupSpec
feature的具體寫法有不少的block組成,這些block對應的feature方法自己的四個階段(setup, stimulus, reponse, cleanup) 。每一個block對應階段示意圖測試
def '測試++'(){ given: def x = 5 when: def result = calculateService.plusPlus(x) then: result == 6 }
then中的斷言在spock中叫condition。好比Java中的Stack在沒有元素時,進行Popup,則會EmptyStackException異常。咱們指望它確實會拋出這個異常,那麼寫法以下
def '異常2'() { given: def stack = new Stack() when: def result = stack.pop() then: EmptyStackException e = thrown() }
它並不會拋出EmptyStackException,咱們要測試這個預期的話,代碼以下:
def '異常2'() { given: def stack = new Stack() stack.push("hello world") when: stack.pop() then: EmptyStackException e = notThrown() }
前面說了when block用來調用,then用來判斷預期結果。但有的時候,咱們的調用和預期判斷並不複雜,那麼能夠用expect將二者合在一塊兒,好比如下兩段代碼等價
when: def x = Math.max(1, 2) then: x == 2
expect: Math.max(1, 2) == 2
def 'cleanup'() { given: def file = new File("/some/path") file.createNewFile() // ... cleanup: file.delete() }
用於清理feature測試執行後的一些設置,好比打開的文件連接。該操做即使測試的feature出異常,依然會被調用
一樣,若是多個測試feature都須要這個cleanup.那麼建議將cleanup的資源提到setup方法中,並在cleanup方法中去清理
爲了讓單元測試可讀性更高,能夠將測試方法中每一部分用文本進行描述,多個描述能夠用and來串聯
def '異常2'() { given:'設置stack對象' def stack = new Stack() and:'其它變量設施' stack.push('hello world') when:'從stack中彈出元素' def result = stack.pop() then:'預期會出現的異常' EmptyStackException e = thrown() }
spock經過標註來擴充單元測試的功能
@Timeout
指定一個測試方法,或一個設置方法最長能夠執行的時間,用於對性能有要求的測試
@Ignore
用於忽略當前的測試方法
@IgnoreRest
忽略除當前方法外的全部方法,用於想快速的測一個方法
@FailsWith
跟exception condition相似
對於有些功能邏輯,其代碼是同樣的,只是須要測試不一樣輸入值。按照先前的介紹,最簡潔的寫法爲:
def "maximum of two numbers1"() { expect: // exercise math method for a few different inputs Math.max(1, 3) == 3 Math.max(7, 4) == 4 Math.max(0, 0) == 1 }
缺點:
因此spock引入了數據表的概念,將測試數據和代碼分開。典型實例以下:
class MathSpec extends Specification { def "maximum of two numbers"() { expect: Math.max(a, b) == c where: a | b || c 1 | 3 || 3 7 | 4 || 7 0 | 0 || 0 } }
def "maximum of two numbers"(int a, int b ,int c) { expect: Math.max(a, b) == c where: a | b | c 1 | 3 | 3 7 | 4 | 4 0 | 0 | 1 }
class MathSpec extends Specification { def "maximum of two numbers"() { expect: Math.max(a, b) == c where: a | b || c 1 | 3 || 3 7 | 4 || 4 0 | 0 || 1 } }
以上測試代碼,數據表中的後兩行會執行失敗。但從測試結果面板中,不能很好的看到詳細結果
使用@Unroll
能夠將每一個迭代的執行結果輸出
能夠看到面板中實際輸出的文本爲測試方法的名稱。若是像在輸出中加上輸入輸出的變量,來詳細展現每一個迭代,能夠在方法名中使用佔位符#variable
來引用變量的值。舉例以下:
@Unroll def "maximum of #a and #b is #c"() { expect: Math.max(a, b) == c where: a | b || c 1 | 3 || 3 7 | 4 || 4 0 | 0 || 1 }
前面的數據表顯示的將數據以表格的形式寫出來。實際上,數據在where block中的準備還有其它多種方式。
where: a << [1, 7, 0] b << [3, 4, 0] c << [3, 7, 0]
從數據庫中查詢
@Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver") def "maximum of two numbers"() { expect: Math.max(a, b) == c where: [a, b, c] << sql.rows("select a, b, c from maxdata") }
使用groovy代碼賦值
where: a = 3 b = Math.random() * 100 c = a > b ? a : b
以上幾種方式能夠混搭。
其中方法名也能夠以豐富的表達式引用where block中的變量
def "person is #person.age years old"() { ... where: person << [new Person(age: 14, name: 'Phil Cole')] lastName = person.name.split(' ')[1] }
有的時候,咱們測試的功能,須要依賴另外的collaborators來測試。這種涉及到多個執行單元之間的交互,叫作交互測試
好比:
class Publisher { List<Subscriber> subscribers = [] int messageCount = 0 void send(String message){ subscribers*.receive(message) messageCount++ } } interface Subscriber { void receive(String message) }
咱們想測Publisher,但Publisher有個功能是是發消息給全部的Subscriber。要想測試Publisher的發送功能確實ok,那麼須要測試Subscriber的確能收到消息。
使用一個實際的Subscriber實現當然能實現這個測試。但對具體的Subscriber實現形成了依賴,這裏須要Mock。使用spock的測試用例以下:
class PublisherTest extends Specification{ Publisher publisher = new Publisher() Subscriber subscriber = Mock() Subscriber subscriber2 = Mock() //建立依賴的Subscriber Mock def setup() { publisher.subscribers << subscriber // << is a Groovy shorthand for List.add() publisher.subscribers << subscriber2 } def "should send messages to all subscribers"() { when: publisher.send("hello") //調用publisher的方法 then: 1*subscriber.receive("hello") //指望subscriber的receive方法能被調用一次 1*subscriber2.receive("hello")//指望subscriber1的receive方法能被調用一次 } }
以上代碼的目的是經過mock來測試當Publisher的send的方法被執行時,且執行參數是'hello'時,subscriber的receive方法必定能被調用,且入參也爲‘hello’
1 * subscriber.receive("hello") | | | | | | | argument constraint | | method constraint | target constraint cardinality
cardinality
定義右邊指望方法執行的次數,這裏是指望執行一次,可能的寫法有以下:
1 * subscriber.receive("hello") // exactly one call 0 * subscriber.receive("hello") // zero calls (1..3) * subscriber.receive("hello") // between one and three calls (inclusive) (1.._) * subscriber.receive("hello") // at least one call (_..3) * subscriber.receive("hello") // at most three calls _ * subscriber.receive("hello") // any number of calls, including zero
target constraint
定義被依賴的對象。可能的寫法以下
1 * subscriber.receive("hello") // a call to 'subscriber' 1 * _.receive("hello") // a call to any mock object
Method Constraint
定義在上述對象上指望被調用的方法,可能的寫法以下:
1 * subscriber.receive("hello") // a method named 'receive' 1 * subscriber./r.*e/("hello") // a method whose name matches the given regular expression // (here: method name starts with 'r' and ends in 'e')
Argument Constraints
對被調用方法,指望的入參進行定義。可能寫法以下:
1 * subscriber.receive("hello") // an argument that is equal to the String "hello" 1 * subscriber.receive(!"hello") // an argument that is unequal to the String "hello" 1 * subscriber.receive() // the empty argument list (would never match in our example) 1 * subscriber.receive(_) // any single argument (including null) 1 * subscriber.receive(*_) // any argument list (including the empty argument list) 1 * subscriber.receive(!null) // any non-null argument 1 * subscriber.receive(_ as String) // any non-null argument that is-a String 1 * subscriber.receive(endsWith("lo")) // any non-null argument that is-a String 1 * subscriber.receive({ it.size() > 3 && it.contains('a') }) // an argument that satisfies the given predicate, meaning that // code argument constraints need to return true of false // depending on whether they match or not // (here: message length is greater than 3 and contains the character a)
1 * subscriber._(*_) // any method on subscriber, with any argument list 1 * subscriber._ // shortcut for and preferred over the above 1 * _._ // any method call on any mock object 1 * _ // shortcut for and preferred over the above
when: publisher.publish("hello") then: 1 * subscriber.receive("hello") // demand one 'receive' call on 'subscriber' _ * auditing._ // allow any interaction with 'auditing' 0 * _ // don't allow any other interaction
默認狀況下,你對Mock實例的方法的調用,會返回該方法返回值的默認值,好比該方法返回的是布爾型,那麼你你調用mock實例中的該方法時,將返回布爾型的默認值false.
若是咱們但願嚴格的限定Mock實例的各方法行爲,能夠經過上述代碼,對須要測試的方法顯示定義指望調用行爲,對其它方法設置指望一次都不調用。以上then block中的0 * _
便是定義這種指望。當除subscriber中的receive和auditing中的全部方法被調用時,該單元測試會失敗,由於這不符合咱們對其它方法調用0次的指望
then: 2 * subscriber.receive("hello") 1 * subscriber.receive("goodbye")
以上兩個指望被調用的順序是隨機的。若是要保證調用順序,使用兩個then
then: 2 * subscriber.receive("hello") then: 1 * subscriber.receive("goodbye")
前面的interaction mock是用來測試被mock的對象,指望方法的調用行爲。好比入參,調用次數。
而stubbing則用來定義被mock的實例,在調用時返回的行爲
總結,前者定義調用行爲指望,後者定義返回行爲指望。且Interaction test 測試的是執行指望或斷言。的stubbing則是用來定義mock的模擬的行爲。
因此stubbing 對mock方法返回值的定義應該放在given block. 而對mock方法自己的調用Interaction test 應該放在then block中。因此stubbing對返回值的定義至關於在定義測試的測試數據。
Stubbing的使用場景也很明確。假設Publisher須要依賴Subscriber方法的返回值,再作下一步操做。那咱們就須要對Subscriber的返回值進行mock,來測試不一樣返回值對目標測試代碼(feature)的行爲。
咱們將上述Subscriber接口對應的方法添加一個返回值
class Publisher { Subscriber subscriber int messageCount = 0 int send(String message){ if(subscriber.receive(message) == 'ok') { this.messageCount++ } return messageCount } } interface Subscriber { String receive(String message) }
測試代碼舉例
Publisher publisher = new Publisher() Subscriber subscriber = Mock() def setup() { publisher.subscriber = subscriber } def "should send msg to subscriber"() { given: subscriber.receive("message1") >> "ok" when: def result = publisher.send("message1") then: result == 1 }
以上代碼表示,模擬subscriber.receive被調用時,且調用參數爲message1,方法返回ok. 而此時指望(斷言)Publisher的send方法,返回的是1
subscriber.receive(_) >> "ok" | | | | | | | response generator | | argument constraint | method constraint target constraint
注意這裏多了response generator,而且沒有interaction test中的Cardinality
返回固定值
subscriber.receive("message1") >> "ok" subscriber.receive("message2") >> "fail"
順序調用返回不一樣的值
subscriber.receive(_) >>> ["ok", "error", "error", "ok"]
第一次調用返回ok,第二次、三次調用返回error。剩下的調用返回ok
根據入參計算返回值
subscriber.receive(_) >> { args -> args[0].size() > 3 ? "ok" : "fail" }
subscriber.receive(_) >> { String message -> message.size() > 3 ? "ok" : "fail" }
上述二者效果都同樣,都是對第一個入參的長度進行判斷,而後肯定返回值
返回異常
subscriber.receive(_) >> { throw new InternalError("ouch") }
鏈式返回值設定
subscriber.receive(_) >>> ["ok", "fail", "ok"] >> { throw new InternalError() } >> "ok"
前三次調用依次返回ok,fail,ok。第四次調用返回異常,以後的調用返回ok
1 * subscriber.receive("message1") >> "ok" 1 * subscriber.receive("message2") >> "fail"
這裏即定義了被mock 的subscriber其方法返回值,也定義了該方法指望被調用多少次。舉例:
Publisher publisher = new Publisher() Subscriber subscriber = Mock() def setup() { publisher.subscriber = subscriber } def "should send msg to subscriber"() { given: 1*subscriber.receive("message1") >> "ok" when: def result = publisher.send("message1") then: result == 1 }
以上寫法,即測試了subscriber.receive被調用了一次,也測試了publisher.send執行結果爲1.若是將Interaction Mock和stubbing組合拆開,像下面這種寫法是不行的:
Publisher publisher = new Publisher() Subscriber subscriber = Mock() def setup() { publisher.subscriber = subscriber } def "should send msg to subscriber"() { given: subscriber.receive("message1") >> "ok" when: def result = publisher.send("message1") then: result == 1 1*subscriber.receive("message1") }
像Junit同樣,在須要測試的類上,使用Idea的幫助快捷鍵,而後彈出
選擇指定的測試框架spock和路徑便可
直接在指定的測試目錄下,新建對應的測試類,注意是新建groovy class
在Idea中,groovy class的圖標是方塊,java class是圓形,注意區分
有可能建完後,對應的圖標是
,說明Ide沒有識別到這是個groovy 類,通常是因爲其代碼有問題,能夠打開該文件,把具體的錯誤修復,好比把註釋去掉之類的
http://spockframework.org/spock/docs/1.1/all_in_one.html#_introduction