清單 11. 如果將一個(gè)值入棧,那么出棧的也應(yīng)該是它,對(duì)嗎?
public void shouldPopPushedValue() throws Exception{
stStack.push("test");
Ensure.that(stStack.pop(), m.is("test"));
}
為 Matcher 挑選 ‘M’
在清單 11 中,我確保 pop() 返回值 “test”。在使用 JBehave 的 Ensure 類(lèi)的過(guò)程中,您常常會(huì)發(fā)現(xiàn),需要一種更豐富的方式來(lái)表達(dá)期望。JBehave 提供了一種 Matcher 類(lèi)型用于實(shí)現(xiàn)豐富的期望,從而滿(mǎn)足了這一需求。而我選擇重用 JBehave 的 UsingMatchers 類(lèi)型(清單 11 中的 m 變量),所以可以使用 is()、and()、or() 等方法和很多其它整潔的機(jī)制來(lái)構(gòu)建更具文學(xué)性的期望。
清單 11 中的 m 變量是 StackBehavior 類(lèi)的一個(gè)靜態(tài)成員,如清單 12 所示。
清單 12. 行為類(lèi)中的 UsingMatchers
private static final UsingMatchers m = new UsingMatchers(){};
有了清單 11 中編寫(xiě)的新的行為方法之后,現(xiàn)在可以來(lái)運(yùn)行它 — 但是這時(shí)會(huì)產(chǎn)生一個(gè)錯(cuò)誤,如清單 13 所示。
清單 13. 新編寫(xiě)的行為不能運(yùn)行
Failures: 1.
1) StackBehavior should pop pushed value:
java.lang.RuntimeException: nothing to pop
怎么回事?原來(lái)是我的 push() 方法還沒(méi)有完工。回到 清單 5,我編寫(xiě)了一個(gè)簡(jiǎn)單的實(shí)現(xiàn),以使我的行為可以運(yùn)行,F(xiàn)在是時(shí)候完成這項(xiàng)工作了,即真正將被推入的值添加到內(nèi)部容器中(如果這個(gè)值不為 null)。如清單 14 所示。
清單 14. 完成 push 方法
public void push(E value) {
if(value == null){
throw new RuntimeException("Can't push null");
}else{
this.list.add(value);
}
}
但是,等一下 — 當(dāng)我重新運(yùn)行該行為時(shí),它仍然失!
清單 15. JBehave 報(bào)告一個(gè) null 值,而不是一個(gè)異常
1) StackBehavior should pop pushed value:
VerificationException: Expected:
same instance as <test>
but got:
null:
至少清單 15 中的失敗有別于清單 13 中的失敗。在這種情況下,不是拋出一個(gè)異常,而是沒(méi)有發(fā)現(xiàn) "test" 值;實(shí)際彈出的是 null。仔細(xì)觀(guān)察 清單 10 會(huì)發(fā)現(xiàn):一開(kāi)始我將 pop() 方法編寫(xiě)為當(dāng)內(nèi)部容器中有項(xiàng)目時(shí),返回 null。問(wèn)題很容易修復(fù)。
清單 16. 是時(shí)候編寫(xiě)完這個(gè) pop 方法了
public E pop() {
if(this.list.size() > 0){
return this.list.remove(this.list.size());
}else{
throw new RuntimeException("nothing to pop");
}
}
但是,如果現(xiàn)在我重新運(yùn)行該行為,我又收到一個(gè)新的錯(cuò)誤。
清單 17. 另一個(gè)錯(cuò)誤
1) StackBehavior should pop pushed value:
java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
仔細(xì)閱讀清單 17 中的實(shí)現(xiàn)可以發(fā)現(xiàn)問(wèn)題:在處理 ArrayList 時(shí),我需要考慮 0。
清單 18. 通過(guò)考慮 0 修復(fù)問(wèn)題
public E pop() {
if(this.list.size() > 0){
return this.list.remove(this.list.size()-1);
}else{
throw new RuntimeException("Nothing to pop");
}
}
棧的邏輯
至此,通過(guò)允許傳遞多個(gè)行為方法,我已經(jīng)實(shí)現(xiàn)了 push() 和 pop() 方法。但是我還沒(méi)有處理?xiàng)5膶?shí)際內(nèi)容,這是與多個(gè) push() 和 pop() 相關(guān)聯(lián)的邏輯,間或出現(xiàn)一個(gè) peek()。
首先,我將通過(guò) shouldPopSecondPushedValueFirst() 行為確保棧的基本算法(先進(jìn)先出)無(wú)誤。
清單 19. 確保典型的棧邏輯
public void shouldPopSecondPushedValueFirst() throws Exception{
stStack.push("test 1");
stStack.push("test 2");
Ensure.that(stStack.pop(), m.is("test 2"));
}
清單 19 中的代碼可以按計(jì)劃運(yùn)行,所以我將實(shí)現(xiàn)另一個(gè)行為方法(在清單 20 中),以確保兩次使用 pop() 都能表現(xiàn)出正確的行為。
清單 20. 更深入地查看棧行為
public void shouldPopValuesInReverseOrder() throws Exception{
stStack.push("test 1");
stStack.push("test 2");
Ensure.that(stStack.pop(), m.is("test 2"));
Ensure.that(stStack.pop(), m.is("test 1"));
}
接下來(lái),我要確保 peek() 能按預(yù)期運(yùn)行。正如 Linda 所說(shuō),peek() 遵從和 pop() 相同的規(guī)則,但是 “應(yīng)該保留棧頂?shù)捻?xiàng)目”。相應(yīng)地,我在清單 21 中實(shí)現(xiàn)了 shouldLeaveValueOnStackAfterPeep() 方法的行為。
清單 21. 確保 peek 保留棧頂?shù)捻?xiàng)目
public void shouldLeaveValueOnStackAfterPeep() throws Exception{
stStack.push("test 1");
stStack.push("test 2");
Ensure.that(stStack.peek(), m.is("test 2"));
Ensure.that(stStack.pop(), m.is("test 2"));
}
由于 peek() 還沒(méi)有定義,因此清單 21 還不能編譯。在清單 22 中,我定義了 peek() 的一個(gè)簡(jiǎn)單的實(shí)現(xiàn)。
清單 22. 當(dāng)前,peek 是必需的
public E peek() {
return null;
}
現(xiàn)在 StackBehavior 類(lèi)可以編譯,但是它仍然不能運(yùn)行。
清單 23. 返回 null 并不奇怪,對(duì)嗎?
1) StackBehavior should leave value on stack after peep:
VerificationException: Expected:
same instance as <test 2>
but got:
null:
在邏輯上,peek() 不會(huì)從內(nèi)部集合中移除 項(xiàng)目,它只是傳遞指向那個(gè)項(xiàng)目的指針。因此,我將對(duì) ArrayList 使用 get() 方法,而不是 remove() 方法,如清單 24 所示。
清單 24. 不要移除它
public E peek() {
return this.list.get(this.list.size()-1);
}
棧為空的情況
現(xiàn)在重新運(yùn)行 清單 21 中的行為,結(jié)果順利通過(guò)。但是,在這樣做的過(guò)程中發(fā)現(xiàn)一個(gè)問(wèn)題:如果棧為空,則 peek() 有怎樣的行為?如果說(shuō)棧為空時(shí)調(diào)用 pop() 會(huì)拋出一個(gè)異常,那么 peek() 是否也應(yīng)該如此?
Linda 對(duì)此沒(méi)有進(jìn)行解釋?zhuān),顯然我需要自己添加新的行為。在清單 25 中,我為 “當(dāng)之前沒(méi)有調(diào)用 push() 時(shí)調(diào)用 peek() 會(huì)怎樣” 這個(gè)場(chǎng)景編寫(xiě)了代碼。