我們都已經發現,前3節我們雖然建立了SimpleStack類,也實作了IsEmpty屬性和Push方法,也通過了測試。但毫無疑問,這完全不合常理。那這節開始,我們要進入三部曲的重整代碼階段。所謂重整,就是将最簡單能使測試通過的代碼,加入必要的代碼,以達到實際所需功能,同時也能讓測試通過。
任務 1 – 将精力從讓測試通過轉移到完成功能上
- 我們需要真正将一個對象推入到棧中,并實際改變了棧,即在推入對象後,棧應當儲存這個對象,棧就應該不為空棧(IsEmpty等于false)。那怎麼去測試呢?Push後驗證IsEmpty 是否為false就行了:
[TestMethod]
public void ThenShouldBeAbleToPushAnItemOntoTheStack()
{
var theStack = new SimpleStack();
theStack.Push(1);
Assert.IsFalse(theStack.IsEmpty);
}
- 運作這個測試,應該不會通過。為什麼失敗呢?
- 别忘了,我們之前為了讓測試通過,隻是簡單将IsEmpty實作為隻讀的永遠傳回true的屬性。現在就要考慮如何讓IsEmpty按實際情況來傳回真實的數值,也展現出Push方法帶來的效果。
- 測試驅動開發同時也被稱為測試驅動設計 ,就是基于我們即将要看到的這些原因,我們正在定義開發的系統應當有什麼行為和怎麼去設計它,用重複方法來表現系統行為。
- 打開SimpleStack類,我們坐下來思考一下這個類。棧應該可以提供儲存某些狀态的機制,特别是它現在儲存何種條目的狀态。
- 将條目儲存在一個清單中,我們就可以檢查清單的大小來确定棧的IsEmpty值。清單也提供了儲存推入的條目的地方。
class SimpleStack
{
ArrayList _items ;
public bool IsEmpty
{
get { return _items.Count == 0; }
}
internal void Push(int p)
{
_items.Add(0);
}
}
- 注意ArrayList 類型未被識别,利用CTRL+. ,輸入System.Collections 命名空間。
- 運作所有的測試。還是失敗!為什麼?測試結果給出的原因是ArrayList未被初始化。
- 添加預設的構造方法,并初始化清單
class SimpleStack
{
ArrayList _items;
public SimpleStack()
{
_items = new ArrayList();
}
…
}
- 再次運作測試,這時候應該全部通過!(綠色)
- 但問題來了,我們怎麼知道Push方法存入正确的對象呢?就如我們上面的代碼,Push方法隻是将零 (0)加入到清單中。
- 是以,我們的測試方法就要繼續做進一步的測試驗證,同時也要添加新的功能(方法)。
- 添加一個新的測試,使之可以驗證推入一個對象後,能夠拉出這個對象,并確定它就是我們剛剛推入的對象:
[TestMethod]
public void ThenShouldBeAbleToPushAndPopAnItemFromTheStack ()
{
var theStack = new SimpleStack();
theStack.Push(1);
int poppedItem = theStack.Pop();
Assert.AreEqual(1, poppedItem);
}
- 再次提示Pop 方法不存在,我們使用Smart Tag幫忙建立新的方法,并利用Quick Search(CTRL+,) 定位到此方法
- 注意VS已确定Pop方法需要傳回一個值,并且知道傳回值的類型,真牛!
internal int Pop()
{
throw new NotImplementedException();
}
- 運作測試方法,會失敗并提示NotImplementedException 意外。我們添加代碼使之可以通過測試:
internal int Pop()
{
int value = (int) _items[0];
_items.RemoveAt(0);
return value;
}
- 再次運作測試,還是失敗。為什麼呢?因為Assert使用AreEqual 方法來驗證推入的資料和拉出的資料是否一樣,結果在Push方法的實作代碼中,我們錯寫了Add(0) 。回到Push方法,将代碼改為:
internal void Push(int p)
{
_items.Add(p );
}
- 再運作測試,所有測試應該成功!
注意:我們現在不是深入讨論TDD的原理和規範上,也不是學習如何正确地實作一個棧類。
但是我們可以繼續設計和建立SimpleStack類,比如思考Pop方法調用在空棧上會出現什麼問題,還有如果嘗試操作一個滿棧的時候會出現什麼情況等等。
為了完成這些設計問題,我們需要建立更多新的測試方式,完成更多的測試方法。
同時,我們也可能留意到這個類存在一個明顯的bug,對于棧應該是先入後出(FILO),但Pop方法永遠都是從開頭拉出對象。那就要繼續設計測試方法來确定Push進入的資料就是Pop出來的資料。