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