qileilove

          blog已經(jīng)轉(zhuǎn)移至github,大家請訪問 http://qaseven.github.io/

          測試驅(qū)動開發(fā)TDD(2)

          今天的TDD練習(xí)又開始了?;仡^看看上一次留下的任務(wù)。
            To-Do-List:
            猜測數(shù)字
            輸入驗證
            生成答案
            輸入次數(shù)
            輸出猜測結(jié)果
            今天我們把輸入驗證和隨機生成答案搞定。
            新建ValidationTest文件。
            分析需求:(1)不重復(fù)。(2)4位(3)數(shù)字。(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);
          }
          }

           按照第一篇的步驟。實現(xiàn)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對應(yīng)這一個IF。也可合并2個CASE??梢杂?^\d{4}$"去Cover"4位數(shù)字"。可以根據(jù)自己的情況去定。
            小步前進不一定要用很小粒度去一步一步走。這樣開發(fā)起來的速度可能很慢。依靠你自身的情況去決定這一小步到底應(yīng)該有多大。正所謂"步子大了容易扯到蛋,步子小了前進太慢"。只要找到最合適自己的步子。才會走的更好。
            這么多IF看起來很蛋疼。有測試。可以放心大膽的重構(gòu)。把每個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;
          }
          }

           為了確保重構(gòu)正確。重構(gòu)之后一定要把所有的CASE在跑一遍,確定所有的都PASS。
            To-Do-List:
            猜測數(shù)字
            輸入驗證
            生成答案
            輸入次數(shù)
            輸出猜測結(jié)果
            驗證搞定了。我們來整整隨機數(shù)。
            分析需求:產(chǎn)品代碼需要一個隨機生成的答案。(1)不重復(fù)。(2)4位(3)數(shù)字。
            這里有個問題:大家都知道隨機數(shù)是個概率的問題。因為每次生成的數(shù)字都不一樣。看看之前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);
          }
          }
            這里我們?nèi)绻裵rivate const string AnswerNumber = "2975";改為隨機的話,那Guesser類測試的結(jié)果是不能確定的。也就是說測試依賴了一些可變的東西。比如:隨機數(shù)、時間等等。
            遇到這種情況應(yīng)該怎么辦呢?一種隨機數(shù)是給產(chǎn)品代碼用,我們可以MOCK另外一種"固定隨機數(shù)"(但是要滿足生成隨機數(shù)的條件)來給測試用。
            還是一樣先寫測試。
          [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);
          }
          }
             實現(xiàn)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。讓兩種隨機數(shù)類繼承。
          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給產(chǎn)品代碼用。AnswerGeneratorForTest給測試代碼用。這樣就可以避免測試依賴可變的問題。
            相應(yīng)的給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());
          }
          ...
          }
            這樣我在測試代碼當中就會給一個不可變的隨機數(shù)。AnswerGeneratorForTest。所以之前的Guesser測試代碼也不會因為每次的隨機數(shù)不一樣導(dǎo)致掛掉。
            產(chǎn)品代碼呢?直接丟AnswerGenerator進去就妥妥地。
            To-Do-List:
            猜測數(shù)字
            輸入驗證
            生成答案
            輸入次數(shù)
            輸出猜測結(jié)果
            OK。今天的收獲。
            (1)小步前進:依靠自身情況決定“小步”應(yīng)該有多大。
           ?。?)重構(gòu):之前的測試是我們重構(gòu)的保障。
            (3)測試依賴:測試不應(yīng)該依賴于一些可變的東西。
            都到這了,有沒有點TDD的感覺。知道TDD的步驟了嗎?
           ?。?)新增一個測試。
            (2)運行所有的測試程序并失敗。
           ?。?)做一些小小的改動。
            (4)運行所有的測試,并且全部通過。
            (5)重構(gòu)代碼以消除重復(fù)設(shè)計,優(yōu)化設(shè)計。
            (6)重復(fù)上面的工作。實現(xiàn)1~5小范圍迭代。直到滿足今天的Story。
            上一篇還有個遺留的問題。我把它記在小本上。
          相關(guān)文章:

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

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

          導(dǎo)航

          統(tǒng)計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 乐亭县| 易门县| 遵义市| 平定县| 昌邑市| 海伦市| 灵丘县| 永州市| 甘德县| 顺昌县| 南雄市| 平凉市| 弥渡县| 旬阳县| 普格县| 吴川市| 潢川县| 怀安县| 敖汉旗| 孟村| 富川| 扶绥县| 望江县| 曲松县| 五指山市| 方城县| 南陵县| 循化| 潢川县| 乡宁县| 凤庆县| 洮南市| 嘉峪关市| 临沂市| 三穗县| 大名县| 长海县| 苍山县| 宁阳县| 宁津县| 济源市|