杰出的 expectation 和 override
在清單 3 中發(fā)生的一些事情是 JBhave 特有的,所以要解釋一下。首先,我創(chuàng)建 Stack 類的一個(gè)實(shí)例,并將它限制為 String 類型(通過(guò) Java 5 泛型)。接下來(lái),我使用 JBehave 的 異?蚣 實(shí)際建模我所期望的行為。 Ensure 類類似于 JUnit 或 TestNG 的 Assert 類型;但是,它增加了一系列方法,提供了更具可讀性的 API(這常被稱作文學(xué)編程)。在清單 3 中,我確保了如果對(duì) null 調(diào)用 push(),則拋出一個(gè) RuntimeException。
JBehave 還引入了一個(gè) Block 類型,它是通過(guò)用所需的行為覆蓋 run() 方法來(lái)實(shí)現(xiàn)的。在內(nèi)部,JBehave 確保期望的異常類型不被拋出(并因此被捕捉),而是生成一個(gè)故障狀態(tài)。您可能還記得,在我前面關(guān)于 用 Google Web Toolkit 對(duì) Ajax 進(jìn)行單元測(cè)試 的文章中,也出現(xiàn)了類似的覆蓋便利類的模式。在那種情況下,覆蓋是通過(guò) GWT 的 Timer 類實(shí)現(xiàn)的。
如果現(xiàn)在運(yùn)行清單 3 中的行為,應(yīng)該看到出現(xiàn)錯(cuò)誤。按照目前編寫(xiě)的代碼,push() 方法不執(zhí)行任何操作。所以不可能生成異常,從清單 4 中的輸出可以看到這一點(diǎn)。
清單 4. 沒(méi)有發(fā)生期望的行為
1) StackBehavior should throw exception upon null push:
VerificationException: Expected:
object not null
but got:
null:
清單 4 中的句子 “StackBehavior should throw exception upon null push” 模擬行為的名稱(shouldThrowExceptionUponNullPush()),并加上類的名稱。 實(shí)際上,JBehave 是在報(bào)告當(dāng)它運(yùn)行所需的行為時(shí),沒(méi)有獲得任何反應(yīng)。當(dāng)然,我的下一步是要使上述行為成功運(yùn)行,為此我檢查 null,如清單 5 所示。
清單 5. 在棧類中增加指定的行為
public void push(E value) {
if(value == null){
throw new RuntimeException("Can't push null");
}
}
當(dāng)我重新運(yùn)行行為時(shí),一切都運(yùn)行得很好,如清單 6 所示。
清單 6. 成功!
Time: 0.021s
Total: 1. Success!
行為驅(qū)動(dòng)開(kāi)發(fā)
清單 6 中的輸出與 JUnit 的輸出是不是很像?這也許不是巧合,對(duì)不對(duì)?如前所述,JBehave 是根據(jù) xUnit 范例建模的,它甚至通過(guò) setUp() 和 tearDown() 提供了對(duì) fixture 的支持。由于我可能在整個(gè)行為類中使用一個(gè) Stack 實(shí)例,我可能也會(huì)將那種邏輯推入(這里并非有意使用雙關(guān)語(yǔ))到一個(gè) fixture 中,正如清單 7 中那樣。注意, JBehave 將與 JUnit 一樣遵循相同的 fixture 規(guī)則 — 也是說(shuō),對(duì)于每個(gè)行為方法,它都運(yùn)行一個(gè) setUp() 和 tearDown()。
清單 7. JBehave 中的 fixture
public class StackBehavior {
private Stack<String> stStack;
public void setUp() {
this.stStack = new Stack<String>();
}
//...
}
對(duì)于接下來(lái)的行為方法,shouldThrowExceptionUponPopWithoutPush() 表示我必須確保它具有類似于 清單 3 中的 shouldThrowExceptionUponNullPush() 的行為。從清單 8 中可以看出,沒(méi)有任何特別神奇的地方 — 有嗎?
清單 8. 確保 pop 的行為
public void shouldThrowExceptionUponPopWithoutPush() throws Exception{
Ensure.throwsException(RuntimeException.class, new Block() {
public void run() throws Exception {
stStack.pop();
}
});
}
您可能已經(jīng)清楚地知道,此時(shí)清單 8 并不會(huì)真正地編譯,因?yàn)?pop() 還沒(méi)有被編寫(xiě)。但是,在開(kāi)始編寫(xiě) pop() 之前,讓我們考慮一些事情。
確保行為
從技術(shù)上講,在這里我可以將 pop() 實(shí)現(xiàn)為無(wú)論調(diào)用順序如何,都只拋出一個(gè)異常。但是當(dāng)我沿著這條行為路線前進(jìn)時(shí),我又忍不住考慮一個(gè)支持我所需要的規(guī)范的實(shí)現(xiàn)。在這種情況下,如果 push() 沒(méi)有被調(diào)用(或者從邏輯上講,棧為空)的情況下確保 pop() 拋出一個(gè)異常,則意味著棧有一個(gè)狀態(tài)。正如之前 Linda 思考的那樣,棧通常有一個(gè) “內(nèi)部容器”,用于實(shí)際持有項(xiàng)目。相應(yīng)地,我可以為 Stack 類創(chuàng)建一個(gè) ArrayList,用于保持傳遞給 push() 方法的值,如清單 9 所示。
清單 9. 棧需要一種內(nèi)部的方式來(lái)持有對(duì)象
public class Stack<E> {
private ArrayList<E> list;
public Stack() {
this.list = new ArrayList<E>();
}
//...
}
現(xiàn)在我可以為 pop() 方法編寫(xiě)行為,即確保當(dāng)棧在邏輯上為空時(shí),拋出一個(gè)異常。
清單 10. pop 的實(shí)現(xiàn)變得更容易
public E pop() {
if(this.list.size() > 0){
return null;
}else{
throw new RuntimeException("nothing to pop");
}
}
當(dāng)我運(yùn)行清單 8 中的行為時(shí),一切如預(yù)期運(yùn)行:由于棧中沒(méi)有存在任何值(因此它的大小不大于 0),于是拋出一個(gè)異常。
接下來(lái)的行為方法是 shouldPopPushedValue(),這個(gè)行為方法很容易指定。我只是 push() 一個(gè)值(“test”),并確保當(dāng)調(diào)用 pop() 時(shí),返回相同的值。