qileilove

          blog已經轉移至github,大家請訪問 http://qaseven.github.io/

          測試驅動開發TDD(2)

          今天的TDD練習又開始了。回頭看看上一次留下的任務。
            To-Do-List:
            猜測數字
            輸入驗證
            生成答案
            輸入次數
            輸出猜測結果
            今天我們把輸入驗證和隨機生成答案搞定。
            新建ValidationTest文件。
            分析需求:(1)不重復。(2)4位(3)數字。(4)不為空。
            按照我們分析出來的4個明確點我們開始寫CASE。
            注意命名!
          [TestClass]
          public class ValidatorTest
          {
          private Validator validator;
          [TestInitialize]
          public void Init()
          {
          validator = new Validator();
          }
          [TestMethod]
          public void should_return_input_must_be_four_digits_when_input_figures_digit_is_not_four_digits()
          {
          var input = "29546";
          validator.Validate(input);
          var actual = validator.ErrorMsg;
          Assert.AreEqual("the input must be four digits.", actual);
          }
          [TestMethod]
          public void should_return_input_must_be_fully_digital_when_input_is_not_all_digital()
          {
          var input = "a4s5";
          validator.Validate(input);
          var actual = validator.ErrorMsg;
          Assert.AreEqual("the input must be fully digital.", actual);
          }
          [TestMethod]
          public void should_return_input_can_not_be_empty_when_input_is_empty()
          {
          var input = "";
          validator.Validate(input);
          var actual = validator.ErrorMsg;
          Assert.AreEqual("the input can't be empty.", actual);
          }
          [TestMethod]
          public void should_return_input_can_not_contain_duplicate_when_input_figures_contain_duplicate()
          {
          var input = "2259";
          validator.Validate(input);
          var actual = validator.ErrorMsg;
          Assert.AreEqual("the input figures can't contain duplicate.", actual);
          }
          }

           按照第一篇的步驟。實現validator。爭取讓所有的CASE都過。
          public class Validator
          {
          public string ErrorMsg { get; private set; }
          public bool Validate(string input)
          {
          if (string.IsNullOrEmpty(input))
          {
          ErrorMsg = "the input can't be empty.";
          return false;
          }
          if (input.Length != 4)
          {
          ErrorMsg = "the input must be four digits.";
          return false;
          }
          var regex = new Regex(@"^[0-9]*$");
          if (!regex.IsMatch(input))
          {
          ErrorMsg = "the input must be fully digital.";
          return false;
          }
          if (input.Distinct().Count() != 4)
          {
          ErrorMsg = "the input figures can't contain duplicate.";
          return false;
          }
          return true;
          }
          }
          Run...
            一個CASE對應這一個IF。也可合并2個CASE。可以用"^\d{4}$"去Cover"4位數字"。可以根據自己的情況去定。
            小步前進不一定要用很小粒度去一步一步走。這樣開發起來的速度可能很慢。依靠你自身的情況去決定這一小步到底應該有多大。正所謂"步子大了容易扯到蛋,步子小了前進太慢"。只要找到最合適自己的步子。才會走的更好。
            這么多IF看起來很蛋疼。有測試。可以放心大膽的重構。把每個IF抽出一個方法。看起來要清晰一些。
          public class Validator
          {
          public string ErrorMsg { get; private set; }
          public bool Validate(string input)
          {
          return IsEmpty(input) && IsFourdigits(input) && IsDigital(input) && IsRepeat(input);
          }
          private bool IsEmpty(string input)
          {
          if (!string.IsNullOrEmpty(input))
          {
          return true;
          }
          ErrorMsg = "the input can't be empty.";
          return false;
          }
          private bool IsFourdigits(string input)
          {
          if (input.Length == 4)
          {
          return true;
          }
          ErrorMsg = "the input must be four digits.";
          return false;
          }
          private bool IsDigital(string input)
          {
          var regex = new Regex(@"^[0-9]*$");
          if (regex.IsMatch(input))
          {
          return true;
          }
          ErrorMsg = "the input must be fully digital.";
          return false;
          }
          private bool IsRepeat(string input)
          {
          if (input.Distinct().Count() == 4)
          {
          return true;
          }
          ErrorMsg = "the input figures can't contain duplicate.";
          return false;
          }
          }

           為了確保重構正確。重構之后一定要把所有的CASE在跑一遍,確定所有的都PASS。
            To-Do-List:
            猜測數字
            輸入驗證
            生成答案
            輸入次數
            輸出猜測結果
            驗證搞定了。我們來整整隨機數。
            分析需求:產品代碼需要一個隨機生成的答案。(1)不重復。(2)4位(3)數字。
            這里有個問題:大家都知道隨機數是個概率的問題。因為每次生成的數字都不一樣。看看之前Guesser類的代碼。
          public class Guesser
          {
          private const string AnswerNumber = "2975";
          public string Guess(string inputNumber)
          {
          var aCount = 0;
          var bCount = 0;
          for (var index = 0; index < AnswerNumber.Length; index++)
          {
          if (AnswerNumber[index]==inputNumber[index])
          {
          aCount++;
          continue;
          }
          if (AnswerNumber.Contains(inputNumber[index].ToString()))
          {
          bCount++;
          }
          }
          return string.Format("{0}a{1}b", aCount, bCount);
          }
          }
            這里我們如果把private const string AnswerNumber = "2975";改為隨機的話,那Guesser類測試的結果是不能確定的。也就是說測試依賴了一些可變的東西。比如:隨機數、時間等等。
            遇到這種情況應該怎么辦呢?一種隨機數是給產品代碼用,我們可以MOCK另外一種"固定隨機數"(但是要滿足生成隨機數的條件)來給測試用。
            還是一樣先寫測試。
          [TestClass]
          public class AnswerGeneratorTest
          {
          [TestMethod]
          public void should_pass_when_answer_generator_number_is_four_digits_and_fully_digital()
          {
          Regex regex = new Regex(@"^\d{4}$");
          var answerGenerator = new AnswerGenerator();
          var actual = regex.IsMatch(answerGenerator.Generate());
          Assert.AreEqual(true, actual);
          }
          [TestMethod]
          public void should_pass_when_answer_generator_number_do_not_repeat()
          {
          var answerGenerator = new AnswerGenerator();
          var actual = answerGenerator.Generate().Distinct().Count() == 4;
          Assert.AreEqual(true, actual);
          }
          }
             實現AnswerGenerator類讓測試通過。
             引用cao大一段代碼稍加修改
          public class AnswerGenerator
          {
          public string Generate()
          {
          var answerNumber = new StringBuilder();
          Enumerable.Range(0, 9)
          .Select(x => new { v = x, k = Guid.NewGuid().ToString() })
          .OrderBy(x => x.k)
          .Select(x => x.v)
          .Take(4).ToList()
          .ForEach(num => answerNumber.Append(num.ToString()));
          return answerNumber.ToString();
          }
          }
            運行測試。
            為了解決測試依賴可變的問題。定義IAnswerGenerator。讓兩種隨機數類繼承。
          public interface IAnswerGenerator
          {
          string Generate();
          }
          [csharp] view plaincopy
          public class AnswerGenerator : IAnswerGenerator
          {
          public string Generate()
          {
          var answerNumber = new StringBuilder();
          Enumerable.Range(0, 9)
          .Select(x => new { v = x, k = Guid.NewGuid().ToString() })
          .OrderBy(x => x.k)
          .Select(x => x.v)
          .Take(4).ToList()
          .ForEach(num => answerNumber.Append(num.ToString()));
          return answerNumber.ToString();
          }
          }
          public class AnswerGeneratorForTest : IAnswerGenerator
          {
          public string Generate()
          {
          return "2975";
          }
          }


           AnswerGenerator給產品代碼用。AnswerGeneratorForTest給測試代碼用。這樣就可以避免測試依賴可變的問題。
            相應的給Guesser類以及測試代碼做個修改。
          public class Guesser
          {
          public string AnswerNumber { get; private set; }
          public Guesser(IAnswerGenerator generator)
          {
          AnswerNumber = generator.Generate();
          }
          public string Guess(string inputNumber)
          {
          ...
          }
          }
          [TestClass]
          public class GuesserTest
          {
          private Guesser guesser;
          [TestInitialize]
          public void Init()
          {
          guesser = new Guesser(new AnswerGeneratorForTest());
          }
          ...
          }
            這樣我在測試代碼當中就會給一個不可變的隨機數。AnswerGeneratorForTest。所以之前的Guesser測試代碼也不會因為每次的隨機數不一樣導致掛掉。
            產品代碼呢?直接丟AnswerGenerator進去就妥妥地。
            To-Do-List:
            猜測數字
            輸入驗證
            生成答案
            輸入次數
            輸出猜測結果
            OK。今天的收獲。
            (1)小步前進:依靠自身情況決定“小步”應該有多大。
            (2)重構:之前的測試是我們重構的保障。
            (3)測試依賴:測試不應該依賴于一些可變的東西。
            都到這了,有沒有點TDD的感覺。知道TDD的步驟了嗎?
            (1)新增一個測試。
            (2)運行所有的測試程序并失敗。
            (3)做一些小小的改動。
            (4)運行所有的測試,并且全部通過。
            (5)重構代碼以消除重復設計,優化設計。
            (6)重復上面的工作。實現1~5小范圍迭代。直到滿足今天的Story。
            上一篇還有個遺留的問題。我把它記在小本上。
          相關文章:

          posted on 2014-01-20 10:09 順其自然EVO 閱讀(211) 評論(0)  編輯  收藏 所屬分類: 測試學習專欄

          <2014年1月>
          2930311234
          567891011
          12131415161718
          19202122232425
          2627282930311
          2345678

          導航

          統計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 同德县| 宁阳县| 元朗区| 洪洞县| 沙湾县| 民县| 凉山| 克什克腾旗| 西峡县| 鸡泽县| 安阳市| 通许县| 万年县| 邓州市| 华亭县| 大埔区| 申扎县| 古蔺县| 青州市| 永城市| 额尔古纳市| 涟水县| 鞍山市| 高台县| 随州市| 唐山市| 中江县| 紫云| 县级市| 交口县| 常宁市| 西吉县| 政和县| 华池县| 海城市| 建阳市| 漯河市| 浮梁县| 奉新县| 齐齐哈尔市| 福州市|