Spock Primer 翻譯

原由

最近要搞groovy介紹,準備作成一系列的東西,參考github上的計劃。
https://github.com/javahub/groovy_hellojava

spock沒有找到翻譯文檔,動手把最重要的一章primer翻譯下,想起了c++ primer。
就看成翻譯練習了。c++

目前項目大使用spock作測試,也是比較推薦的方式。簡潔清爽git

Spock Primer

這一章假設你有關於groovy與單元測試基本知識。若是你是java開發者,但沒據說過groovy,別擔憂,groovy對java開發者來講很是友好。事實上,groovy相關抓喲設計目標是與java共存腳本語言。所以只須要繼續並查看groovy docgithub

這一章目標是教會你足夠寫真實spec的spock,而且激起你的食慾數據庫

學習更多關於groovy,點http://groovy-lang.org/.編程

學些更多關於單元測試,點 http://en.wikipedia.org/wiki/Unit_testing.安全

術語

讓咱們開始一些定義:spock 讓你寫描述指望特性展現spec做爲系統關注點,系統感興趣關注點能夠是一個簡單的類和整個系統中的任何事,而且被稱爲spec下的系統(sus)。一個特性描述開始於spec sus的臨時快照而且做爲一個合做者。快照被稱做特性fixture網絡

接下來的部分沿着在spock的spec可能組合構建blocks。一個常規spec能使用它們的中一部分。架構

Specification

一個spec表現爲繼承spock.lang.Specification的一個groovy 類。spec名字一般描述系統或系統操做。例如: CustomerSpec, H264VideoPlayback, and ASpaceshipAttackedFromTwoSides是一個規範合理的名稱ide

spec類包含一系統有用的方法,此處它含義爲spec用Sputnik在junit中運行,spock的junit runner,感謝Sputnik,spock spec能運行在大多數的ides和構建工具

Fields

def obj = new ClassUnderSpecification()
def coll = new Collaborator()

對象實例字段是一個好的地方去存儲屬於spec fixture 。它是一個好的實踐在正確初始化他們在定義時候。(語義上它等價於初始化他們在setup()方法).存儲這些實例字段的對象不該該分享在feature方法之間。取而代之,每一個特性方法獲得他們本身的初始化對象。這個幫助在相互間獨立隔離特性方法,提供一個好的目標。

@Shared res = new VeryExpensiveResource()

一些時候你須要分享一個對象在特性方法之間。例如對象可能建立很昂貴,或者你可能想在不一樣特性方法指南交互。爲了實現這一點,定義@Shared 字段,它最好事初始化正確字段在一個定義點(語義上等價等價與setupSpec)

static final PI = 3.141592654

靜態字段應該是常量,寧一方面shared字段是完美的,由於他的語義表現了更好的定義。

Fixture Methods

Fixture Methods
def setup() {}          // run before every feature method
def cleanup() {}        // run after every feature method
def setupSpec() {}     // run before the first feature method
def cleanupSpec() {}   // run after the last feature method

fixture方法負責在環境設置清理在每一個特性方法運行。一般它是一個好的idea使用刷新fixture 方法在爲每一個fixture方法,在setup() and cleanup() 作這個。偶爾對特性方法分享是由意義的。他實現了分享使用shared字段與setupSpec() and cleanupSpec() methods。
全部fixture方法都是可選擇的。

說明:setupSpec cleanupSpec 方法不能飲用實例字段

Feature Methods

def "pushing an element on the stack"() {
// blocks go here
}

Feature 方法是一個spec核心,他描述了你期待系統的在sus下的特性集。根據約定,特性方法唄命名字符串常量。試着爲你的特性方法選擇一個好的方法。而且隨心使用你喜歡的特性。

概念,一個特性方法包括四個語義段:
設置特性的fixture
提供sus的刺激
描述系統期待的結果
清理fixture

Blocks

spock實現支持每一個feature方法概念語法階段做爲一個特性方法。對結束來講,特性方法被構建爲一個叫作blocks。blocks開始於一個label而且擴展開始下一個label或者方法結束。這個有六個blocks:setup, when, then, expect, cleanup, and where blocks.。任何語句在方法開始與第一次
隱含的setup block之間。

一個特性方法必須至少一個清晰的label,實際上,一個直接的block表現。Blocks 決定一個方法不一樣部分而且不能嵌套。

Blocks2Phases
這個右側圖片展現block如何匹配一個特性方法語法概念。那裏的block有一個特殊的角色,被簡單的展示出來,但首先讓咱們有一個近距離查看這個block

Setup Blocks

setup:
def stack = new Stack()
def elem = "push me"

這個setup block 你能作任何你想setpup工做爲你想描述的特性。他可能不能被前面其餘block處理,而且不能重複。一個setup block 沒有任何特殊的語義。setup: label時可選的並被提交。結果在一個直接設置的block。given: label是一個setup的別名,而且一些時候致使更多的刻的特性方法描述。

When and Then Blocks

when與then blocks
when: //刺激
then://響應

when與then blocks 一直一塊兒出現。他們描述一個刺激發生並響應期待。
when blocks能包含任意代碼;
then blocks被禁止使用在條件,異常條件,交互與變量定義。
一個特性方法可能匹配多個when-then 塊

Conditions

Conditions被描述爲一個期待的狀態,必須像junit的assertions。然而,conditions被寫作一個描述布爾表達式,消除爲必需的assertion API(偏偏更多的是一個條件唄處理爲一個非布爾表達式,將會被做爲處理根據groovy truth)讓咱們來看一些場景在如下

stack.size() == 2
|     |      |
|     1      false

儘量保持在每一個小特性方法裏的條件數量。一到五個是一個好的指導。若是你有更多,你得問本身,若是在一個場景下有多個不相關的特性被說起。答案若是是yes,在幾個小的特性方法重構。若是你的場景時只有不一樣的值,考慮使用數據表驅動(spock帶有這個特性)

若是一個條件沒經過,spock提供什麼樣的反饋。讓咱們試並改變第二個場景

如你所見,spock在一個場景執行期間,捕獲全部值處理在,並在一個容易理解的形式表現他們。幹得漂亮,不是麼?

Implicit and explicit conditions

場景必須有一個 then blocks and expect blocks其中之一。出了調用空方法雨歸類表達式做爲交互,全部頂級表達式在這些塊裏面是隱含做爲場景對待。在別的地方使用題哦安,你須要設計他們使用groovy的assert關鍵字。

def setup() {
stack = new Stack()
assert stack.empty
}

若是顯試條件被觸發,他將處理一些相同的診斷信息做爲隱含條件。

Exception Conditions

異常場景唄使用描述做爲一個when 語句塊應該拋出一個異常。他們被定義使用 thrown()方法,被傳遞期待的異常類型。例如,描述被彈出一個空stack 應該拋出EmptyStackException,你能像下面這樣寫

when:
stack.pop()

then:
thrown(EmptyStackException)
stack.empty

如你所見,異常場景多是跟別的場景相關。這個特別有用爲對詳細說明期待內容做爲異常,訪問這個異常,首先綁定一個變量。

when:
stack.pop()

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

寧外,你也可使用上面語法做爲一個輕微有變化

when:
stack.pop()

then:
EmptyStackException e = thrown()
e.cause == null

這個語法有兩個小優點,首先,異常變量時強類型,讓它很是容易被ide補全。其次,場景讀起來有一點更像句子。注意,若是沒有異常類型被傳遞在thrown方法,它從左邊的變量類型被推斷

一些時候咱們須要表達一個異常不該該被拋出。例如,讓咱們嘗試表達hashmap不該該接受null key。

def "HashMap accepts null key"() {
  setup:
  def map = new HashMap()
  map.put(null, "elem")
}

一些時候咱們須要表達一個異常不該該被拋出。例如,讓咱們嘗試表達hashmap不該該接受null key。

這個表示不能揭示代碼的含義。 是否有人離開構建以前就完成實現了這個方法。畢竟,場景在哪裏? 幸運是咱們能作更好。

def "HashMap accepts null key"() {
  setup:
def map = new HashMap()

  when:
  map.put(null, "elem")

  then:
  notThrown(NullPointerException)
}

經過使用notThrown。咱們能使清晰在一個特殊的NullPointerException 不該該被拋出。(按照Map.put()方法的約定,它是正確的事情不支持null keys在map上使用。)然而,方法將會失敗若是有任何逼的異常拋出。

Interactions

儘管條件描述了一個對象的狀態,交互描述了對象互相間關係。交互被轉用於一章。因此咱們只快速給一個例子在這。假設咱們想描述從一個發佈者到訂閱者的事件流。代碼以下

def "events are published to all subscribers"() {
    def subscriber1 = Mock(Subscriber)
    def subscriber2 = Mock(Subscriber)
    def publisher = new Publisher()
    publisher.add(subscriber1)
    publisher.add(subscriber2)

when:
publisher.fire("event")

then:
1 * subscriber1.receive("event")
1 * subscriber2.receive("event")

Expect Blocks

一個expect block 比then block有更多的限制。他可能包含場景與變量定義。它是有用的情形當他更加天然描述刺激與期待結果響應在一個簡單的表達式裏。例如,比較下面兩個描述 Math.max()方法。

when:
    def x = Math.max(1, 2)
    then:
    x == 2
    expect:
    Math.max(1, 2) == 2

儘管兩個片斷時語義等價。第二個是更清晰選擇。做爲一個指引,使用when-then 去描述帶有反作用。而且期待描述純碎的函數方法。

TIP

利用groovy jdk方法像any() every()去建立更多表現力與簡潔的條件。

Cleanup Blocks

setup:
def file = new File("/some/path")
file.createNewFile()
// ...
cleanup:
file.delete()

cleanup block

只能跟着where block後面。而且不能重複。像一個cleanup 方法,它做爲釋放資源使用做爲一個特性方法使用,運行即便特性方法拋出異常。所以,cleanup block 必須防護編程。在最糟糕的狀況下,它必須優雅的處理在特性方法裏拋出異常的第一個語句。而且全部本地變量有默認值。

TIPS

groovy的安全引用操做精簡寫防護是代碼
對象級別的spec 一般不用cleanup方法。做爲惟一資源他們消費內存,自動被回收經過垃圾收集齊。更多粗力度spec,可是,可能使用clean 塊 清理文件系統關閉數據庫鏈接,關閉網絡服務

def "computing the maximum of two numbers"() {
expect:
Math.max(a, b) == c

where:
a << [5, 3]
b << [1, 9]
c << [5, 9]

}
This where block effectively creates two "versions" of the feature method: One where a is 5, b is 1, and c is 5, and another one where a is 3, b is 9, and c is 9.

這個where 塊建立兩個版本特性方法很是有效:One where a is 5, b is 1, and c is 5, and another one where a is 3, b is 9, and c is 9.

where block 將會在數據驅動測試章節解釋

Helper Methods

一些時候特性方法增加巨大在 and/or包含重複代碼。在這種情形下引入一個多個幫助方法會有意義。兩個好的候選者爲幫助方法是setup/cleanup 邏輯與複雜場景。分解出前者是直接的,讓咱們看下場景

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

then:
pc.vendor == "Sunny"
pc.clockRate >= 2333
pc.ram >= 4096
pc.os == "Linux"

}
若是碰巧你也是電腦極客,你更願意pc配置是詳細的,或者你可能想比較供應從同的商店。所以,讓咱們分解條件

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

then:
matchesPreferredConfiguration(pc)

}

def matchesPreferredConfiguration(pc) {
pc.vendor == "Sunny"
&& pc.clockRate >= 2333
&& pc.ram >= 4096
&& pc.os == "Linux"

}
新的幫助方法matchesPreferredConfiguration 由簡單的布爾表達式組成做爲結果返回。
這是好的除了那些一個不充分的供應被描述。

Condition not satisfied:

matchesPreferredConfiguration(pc)
|                             |
false                         ...
Not very helpful. Fortunately, we can do better:

void matchesPreferredConfiguration(pc) {
  assert pc.vendor == "Sunny"
  assert pc.clockRate >= 2333
  assert pc.ram >= 4096
  assert pc.os == "Linux"
}

不是頗有什麼幫助。幸運的事,咱們能夠作更好
當在一個幫助方法分解場景時,必須思考兩個點:首先,隱含條件必須轉換爲顯性條件使用assert關鍵字。其次,幫助方法必須返回空類型。不然,spock會解釋返回值爲失敗條件,這並非咱們想要的。

做爲猜測,改進的幫助方法告訴咱們確切什麼是錯的。

Condition not satisfied:

  assert pc.clockRate >= 2333
         |  |         |
         |  1666      false
         ...

條件不是滿意:

最後一個建議:儘管重用代碼是一個好事情,但不要是他太遠。更聰明使用fixture 和幫助方法能增長耦合在特性方法間。若是你重用太多或者有錯誤代碼,你將以脆弱和艱難改進的spec結束。

Specifications as Documentation

寫得好的spec做爲一個源碼信息價值點。尤爲是對高級別spec目標是更普遍的受衆不知是開發者(架構師 領域專家 客戶等)它是有道理的提供更多信息使用天然語言比只是使用規格與特性的名稱。所以,spock提供了一種方式附屬文本話的描述使用塊。

setup: "open a database connection"
  // code goes here
  Individual parts of a block can be described with and::

  setup: "open a database connection"
  // code goes here

  and: "seed the customer table"
  // code goes here

  and: "seed the product table"

一個and 標籤 跟着描述能插入到特性方法的任何位置,沒有改變方法語義。

在行爲驅動測試,面向客戶的特性被描述在一個given-when-then 格式。spock直接支持這個風格的spec使用given標籤

given: "an empty bank account"
  // ...

  when: "the account is credited $10"
  // ...

  then: "the account's balance is $10"
  // ...
  As noted before, given: is just an alias for setup:.

做爲以前的提示,given知識setup的一個別名

塊描述不該該出如今源碼裏。但應該在運行時可用對spock運行時。塊描述用法的計劃被加強診斷信息而且文本化的報告被全部利益相關者一樣理解。

擴展
如所見,spock提供許多寫spec功能。然而老是有這種時候,當一些別的東西被須要。所以,spock提供了一個基於攔截的擴展機制。擴展經過註解被直接激活。當前,spock附帶一下指令

@Timeout  
Sets a timeout for execution of a feature or fixture method.

設置超時時間爲特性方法或者fixture方法

@Ignore 
Ignores a feature method.

忽略特性方法

@IgnoreRest 
Ignores all feature methods not carrying this annotation. Useful for quickly running just a single method.
不移除這個註釋,忽略全部特性方法不去。對快速運行單個方法很是有用。

期待一個特性方法去完成打斷。@FailsWith有兩個用例:首先,文檔化咱們知道的bug不須要馬上解釋。其次,替換異常條件在一個肯定的角落用例在後面不能使用。在全部的用例,異常條件不是優選。

學習怎樣實現你本身的指令與擴展,去看擴展章節

Comparison to JUnit

儘管spock使用不一樣的技術,大部分概念和特性唄激活從junit。這有一個粗的比較
comparison

|Spock|JUnit| |--|--| |Specification|Test class| |setup()|@Before| |cleanup()|@After| |setupSpec()|@BeforeClass||cleanupSpec()|@AfterClass| |Feature|Test| |Feature method|Test method||Data-driven feature|Theory||Condition|Assertion||Exception condition|@Test(expected=…​)| |Interaction|Mock expectation (e.g. in Mockito)|

相關文章
相關標籤/搜索