測試驅動開發TDD(3)
上一篇我剩下的To-Do-List:
猜測數字
輸入驗證
生成答案
輸入次數
輸出猜測結果
今天爭取全部搞定。
現在我們Guesser、生成答案、輸入驗證都有了。把它們組裝成一起搖身一變成一個Game!
用一個類把這些職責單一的小模塊組合起來。我暫且稱它為GameManager.
分析剩下的需求。(1)輸入6次GameOver.(2)輸入合法數字返回猜測結果。(3)游戲結束提示重新開始游戲。(4)中途輸入exit 退出游戲。(5)輸入正確答案,GameOver。
先把之前寫的Guesser提出一個接口。
public interface IGuesser { string AnswerNumber { get; } string Guess(string inputNumber); } |
public class Guesser :IGuesser { public string AnswerNumber { get; private set; } public Guesser(IAnswerGenerator generator) { AnswerNumber = generator.Generate(); } public string Guess(string inputNumber) { ... } } |
Test First.
新建GameManagerTest
[TestClass] public class GameManagerTest { [TestMethod] public void should_return_game_over_when_input_times_is_six_and_result_is_wrong() { IGuesser guess = new Guesser(new AnswerGeneratorForTest()); var game = new GameManager(guess); var input = "1368"; var maxtimes = 6; var actual = false; for (var time = 0; time < maxtimes; time++) { game.Guess(input); } actual = game.IsGameOver; Assert.AreEqual(true, actual); } } |
實現GameManager讓測試通過。
public class GameManager { private readonly IGuesser guesser; public bool IsGameOver { get; private set; } private const int MAX_TIMES = 6; private int times; public GameManager(IGuesser guesser) { this.guesser = guesser; } public void Guess(string input) { times++; IsGameOver = times == MAX_TIMES; guesser.Guess(input); } } |
ok
猜測數字
輸入驗證
生成答案
輸入次數
輸出猜測結果
輸入猜測結果。這里包含一個猜對的情況下應該返回"you win"并且Gameover。其他輸入返回的結果,在Guesser和validator中已經Cover了。挑幾個來測試一下輸入輸出。
[TestClass] public class GameManagerTest { private _gameManager _game; [TestInitialize] public void Init() { IGuesser guesser = new Guesser(new AnswerGeneratorForTest()); _game = new _gameManager(guesser); } [TestMethod] public void should_return__game_over_when_input_times_is_six_and_result_is_wrong() { const string input = "1368"; const int maxtimes = 6; var actual = false; for (var time = 0; time < maxtimes; time++) { _game.Guess(input); } actual = _game.Is_gameOver; Assert.AreEqual(true, actual); } [TestMethod] public void should_return_you_win_and__game_is_over_when_input_is_equal_answer_number() { const string input = "2975"; var actual = _game.Guess(input); Assert.AreEqual("You win!", actual); Assert.AreEqual(true, _game.Is_gameOver); } [TestMethod] public void should_return_try_again_input_must_be_four_digits_when_input_is_not_equal_four_digits() { const string input = "15243"; var actual = _game.Guess(input); Assert.AreEqual("try again the input must be four digits.", actual); } [TestMethod] public void should_return_try_again_input_can_not_be_empty_when_input_is_empty() { const string input = ""; var actual = _game.Guess(input); Assert.AreEqual("try again the input can't be empty.", actual); } [TestMethod] public void should_return_try_again_input_must_be_fully_digital_when_input_is_not_all_digital() { const string input = "a4sw"; var actual = _game.Guess(input); Assert.AreEqual("try again the input must be fully digital.", actual); } } |
修改GameManager類,讓所有CASE PASS.
public string Guess(string input) { times++; IsGameOver = times == MAX_TIMES; var validator = new Validator(); if (!validator.Validate(input)) { return "try again " + validator.ErrorMsg; } var result = guesser.Guess(input); if (result == "4a0b") { IsGameOver = true; return "You win!"; } return "try again result is " + result + "."; } |
猜測數字
輸入驗證
生成答案
輸入次數
輸出猜測結果
最后:完善GameManager類的work flow。
public class GameManager { private const int MAX_TIMES = 6; private int times; private readonly IGuesser guesser; public bool IsGameOver { get; private set; } public GameManager(IGuesser guesser) { this.guesser = guesser; } private void Start() { times = 0; IsGameOver = false; OutputGameHeader(); } public void Run() { Start(); while (!IsGameOver) { Console.WriteLine(); Console.WriteLine(string.Format("[The {0}st time ] : please input number!", times + 1)); var input = Console.ReadLine(); if (IsExit(input)) continue; var result = Guess(input); Console.WriteLine(result); } OutputGamefooter(); } private bool IsExit(string input) { if (input.ToLower().Trim() == "exit") { Console.WriteLine("Make sure to exit game?(Y/N)"); var readLine = Console.ReadLine(); if (readLine != null) { var isexit = readLine.ToLower().Trim(); if (isexit == "y") { IsGameOver = true; } } return true; } return false; } public string Guess(string input) { times++; IsGameOver = times == MAX_TIMES; var validator = new Validator(); if (!validator.Validate(input)) { return "try again " + validator.ErrorMsg; } var result = guesser.Guess(input); if (result == "4a0b") { IsGameOver = true; return "You win!"; } return "try again result is " + result + "."; } private void OutputGameHeader() { Console.Clear(); Console.WriteLine(" --- Game Start! ---"); Console.WriteLine("---------------------------------------------------------------"); Console.WriteLine("| You can input a number or input exit for exiting this game! |"); Console.WriteLine("---------------------------------------------------------------"); } private void OutputGamefooter() { Console.WriteLine("--------------------------------"); Console.WriteLine("| Game Over! [Answer] is " + guesser.AnswerNumber + " |"); Console.WriteLine("--------------------------------"); } } Program.cs class Program { static void Main(string[] args) { var isRepeatGame = false; do { IGuesser guesser = new Guesser(new AnswerGenerator()); var game = new GameManager(guesser); game.Run(); Console.WriteLine("Try again?(Y/N)"); var line = Console.ReadLine(); if (line == null) continue; var readLine = line.ToLower().Trim(); isRepeatGame = readLine == "y"; } while (isRepeatGame); } } |
跑下所有的測試。
到這里。這個游戲的基本功能算做完了。做的比較簡單。測試和產品代碼也比較隨意。
大家也可以試著做一做。感受感受測試驅動產品代碼。
運行效果
下面是我在實踐TDD中遇到的一些問題、以及我個人對它們的理解。
(1)先寫測試在寫代碼開發速度降低了。
開發前期速度確實很慢。當項目越來越大。越來越復雜的時候。改一個bug,或者修改story之后。如何確保產品代碼是否正確。手動測試需要多少時間呢?或者調試的時間有多長呢?有了這些測試。可以最大限度的節省你的時間。也許跑一遍測試就可以定位BUG。測試過了。你的修改也就沒問題了。
(2)TDD驅動出來的代碼。維護性、擴展性如何。
TDD有益于設計。把一個復雜的需求拆分成若干個小模塊的過程當中,其實就在思考設計。如何保證每個小模塊的職責單一。
(3)后寫測試可以不?
我的理解是:第一 測試驅動開發是通過測試去驅動產品代碼的,如果遇到一個很難的模塊(你寫不出來的),就可以通過測試一點點的去驅動。第二 如果在開發之后寫測試的話,問問自己,會寫嗎?或者是能寫全嗎?如果有足夠的信心。也可以寫。
(4)TDD的產物可以方便后期的測試。
試想一下,項目到了后期,在龐大的系統面前,我們要修改某個類、某個方法、修改某個BUG、添加或擴展某個功能的時候。是不感覺特沒安全感?會不會為了去找由修改一個小功能而導致其他功能崩潰的原因而抓狂呢?會不會為了定位一個BUG而在各個類之間不斷的徘徊呢?會不會感覺到牽一發動全身的感覺呢?軟件的壞味等等都會導致這種問題出現。到時候不但被老板罵,還要加班!還要陷入無止盡的各種調試中。(最主要的是你把TEAM里的MM給連累了!)。想避免這種問題嗎?想盡快定位BUG的位置嗎?如果你想!
說點體會:
(1)清晰的測試方法命名,讓我們省去了文檔維護的時間。
(2)站在不同角度分析用戶需求。拆分Story有益于你的設計(DI)。
(3)所有TDD留下來的測試。可用來做自動化測試,無論你是修BUG,或者添功能。都可以通過自動化測試,快速得到反饋。
(4)有了重構的保證。
(5)進度可視化。可以看出一個復雜的模塊,自己完成了多少。(有多少CASE通過)。
(6)小范圍迭代。把當前工作重心放在當前這個“小步”上。
需要注意的:
(1)把握好測試的粒度。
(2)測試要盡可能的簡單。
(3)測試不要依賴可變。
(4)斷言優先。
其實TDD真正有威力的地方是Story劃分。以及復雜模塊代碼的驅動。
以后如果有機會。能理解的更深。會把這兩個寫出來與大家分享分享。
對這段時間的TDD做個小總結。TDD的范圍比較廣。而且也比較抽象。以后會加深對TDD的理解。也會把這些記錄下來。
代碼比較簡單。沒源碼!
相關文章:
posted on 2014-01-22 09:42 順其自然EVO 閱讀(412) 評論(0) 編輯 收藏 所屬分類: 敏捷測試