测试驱动开发TDD(二)

前端之家收集整理的这篇文章主要介绍了测试驱动开发TDD(二)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

@H_403_2@

大家好:

今天的TDD练习又开始了。回头看看上一次留下的任务。

To-Do-List:

猜测数字@H_403_2@
输入验证
生成答案
输入次数
输出猜测结果

今天我们把输入验证和随机生成答案搞定。

新建ValidationTest文件

分析需求:(1)不重复。(2)4位(3)数字。(4)不为空。

按照我们分析出来的4个明确点我们开始写CASE。

注意命名!

[csharp] view plain copy
  1. [TestClass]@H_403_2@@H_403_2@
  2. public@H_403_2@@H_403_2@class@H_403_2@ValidatorTest@H_403_2@@H_403_2@
  3. {@H_403_2@
  4. private@H_403_2@Validatorvalidator;@H_403_2@@H_403_2@
  5. [TestInitialize]@H_403_2@
  6. public@H_403_2@@H_403_2@void@H_403_2@Init()@H_403_2@@H_403_2@
  7. {@H_403_2@
  8. validator=new@H_403_2@Validator();@H_403_2@@H_403_2@
  9. }@H_403_2@
  10. @H_403_2@
  11. [TestMethod]@H_403_2@
  12. public@H_403_2@@H_403_2@void@H_403_2@should_return_input_must_be_four_digits_when_input_figures_digit_is_not_four_digits()@H_403_2@@H_403_2@
  13. {@H_403_2@
  14. varinput="29546"@H_403_2@;@H_403_2@@H_403_2@
  15. validator.Validate(input);@H_403_2@
  16. varactual=validator.ErrorMsg;@H_403_2@
  17. Assert.AreEqual("theinputmustbefourdigits."@H_403_2@,actual);@H_403_2@@H_403_2@
  18. }@H_403_2@
  19. @H_403_2@
  20. [TestMethod]@H_403_2@
  21. public@H_403_2@@H_403_2@void@H_403_2@should_return_input_must_be_fully_digital_when_input_is_not_all_digital()@H_403_2@@H_403_2@
  22. {@H_403_2@
  23. varinput="a4s5"@H_403_2@;@H_403_2@@H_403_2@
  24. validator.Validate(input);@H_403_2@
  25. varactual=validator.ErrorMsg;@H_403_2@
  26. Assert.AreEqual("theinputmustbefullydigital."@H_403_2@,actual);@H_403_2@@H_403_2@
  27. }@H_403_2@
  28. @H_403_2@
  29. [TestMethod]@H_403_2@
  30. public@H_403_2@@H_403_2@void@H_403_2@should_return_input_can_not_be_empty_when_input_is_empty()@H_403_2@@H_403_2@
  31. {@H_403_2@
  32. varinput=""@H_403_2@;@H_403_2@@H_403_2@
  33. validator.Validate(input);@H_403_2@
  34. varactual=validator.ErrorMsg;@H_403_2@
  35. Assert.AreEqual("theinputcan'tbeempty."@H_403_2@,actual);@H_403_2@@H_403_2@
  36. }@H_403_2@
  37. @H_403_2@
  38. [TestMethod]@H_403_2@
  39. public@H_403_2@@H_403_2@void@H_403_2@should_return_input_can_not_contain_duplicate_when_input_figures_contain_duplicate()@H_403_2@@H_403_2@
  40. {@H_403_2@
  41. varinput="2259"@H_403_2@;@H_403_2@@H_403_2@
  42. validator.Validate(input);@H_403_2@
  43. varactual=validator.ErrorMsg;@H_403_2@
  44. Assert.AreEqual("theinputfigurescan'tcontainduplicate."@H_403_2@,actual);@H_403_2@@H_403_2@
  45. }@H_403_2@
  46. }@H_403_2@
按照第一篇的步骤。实现validator。争取让所有的CASE都过。@H_403_2@

[csharp] view plain copy
  1. public@H_403_2@@H_403_2@class@H_403_2@Validator@H_403_2@@H_403_2@
  2. {@H_403_2@
  3. public@H_403_2@@H_403_2@string@H_403_2@ErrorMsg{@H_403_2@get@H_403_2@;@H_403_2@private@H_403_2@@H_403_2@set@H_403_2@;}@H_403_2@@H_403_2@
  4. @H_403_2@
  5. public@H_403_2@@H_403_2@bool@H_403_2@Validate(@H_403_2@string@H_403_2@input)@H_403_2@@H_403_2@
  6. {@H_403_2@
  7. if@H_403_2@(@H_403_2@string@H_403_2@.IsNullOrEmpty(input))@H_403_2@@H_403_2@
  8. {@H_403_2@
  9. ErrorMsg="theinputcan'tbeempty."@H_403_2@;@H_403_2@@H_403_2@
  10. return@H_403_2@@H_403_2@false@H_403_2@;@H_403_2@@H_403_2@
  11. }@H_403_2@
  12. if@H_403_2@(input.Length!=4)@H_403_2@@H_403_2@
  13. {@H_403_2@
  14. ErrorMsg="theinputmustbefourdigits."@H_403_2@;@H_403_2@@H_403_2@
  15. return@H_403_2@@H_403_2@false@H_403_2@;@H_403_2@@H_403_2@
  16. }@H_403_2@
  17. varregex=new@H_403_2@Regex(@@H_403_2@"^[0-9]*$"@H_403_2@);@H_403_2@@H_403_2@
  18. if@H_403_2@(!regex.IsMatch(input))@H_403_2@@H_403_2@
  19. {@H_403_2@
  20. ErrorMsg="theinputmustbefullydigital."@H_403_2@;@H_403_2@@H_403_2@
  21. return@H_403_2@@H_403_2@false@H_403_2@;@H_403_2@@H_403_2@
  22. }@H_403_2@
  23. if@H_403_2@(input.Distinct().Count()!=4)@H_403_2@@H_403_2@
  24. {@H_403_2@
  25. ErrorMsg="theinputfigurescan'tcontainduplicate."@H_403_2@;@H_403_2@@H_403_2@
  26. return@H_403_2@@H_403_2@false@H_403_2@;@H_403_2@@H_403_2@
  27. }@H_403_2@
  28. return@H_403_2@@H_403_2@true@H_403_2@;@H_403_2@@H_403_2@
  29. }@H_403_2@
  30. }@H_403_2@

Run...


一个CASE对应这一个IF。也可合并2个CASE。可以用"^\d{4}$"去Cover"4位数字"。可以根据自己的情况去定。

小步前进不一定要用很小粒度去一步一步走。这样开发起来的速度可能很慢。依靠你自身的情况去决定这一小步到底应该有多大。正所谓"步子大了容易扯到蛋,步子小了前进太慢"。只要找到最合适自己的步子。才会走的更好。

这么多IF看起来很蛋疼。有测试。可以放心大胆的重构。把每个IF抽出一个方法。看起来要清晰一些。

[csharp] view plain copy
  1. public@H_403_2@@H_403_2@class@H_403_2@Validator@H_403_2@@H_403_2@
  2. {@H_403_2@
  3. public@H_403_2@@H_403_2@string@H_403_2@ErrorMsg{@H_403_2@get@H_403_2@;@H_403_2@private@H_403_2@@H_403_2@set@H_403_2@;}@H_403_2@@H_403_2@
  4. @H_403_2@
  5. public@H_403_2@@H_403_2@bool@H_403_2@Validate(@H_403_2@string@H_403_2@input)@H_403_2@@H_403_2@
  6. {@H_403_2@
  7. return@H_403_2@IsEmpty(input)&&IsFourdigits(input)&&IsDigital(input)&&IsRepeat(input);@H_403_2@@H_403_2@
  8. }@H_403_2@
  9. @H_403_2@
  10. private@H_403_2@@H_403_2@bool@H_403_2@IsEmpty(@H_403_2@string@H_403_2@input)@H_403_2@@H_403_2@
  11. {@H_403_2@
  12. if@H_403_2@(!@H_403_2@string@H_403_2@.IsNullOrEmpty(input))@H_403_2@@H_403_2@
  13. {@H_403_2@
  14. return@H_403_2@@H_403_2@true@H_403_2@;@H_403_2@@H_403_2@
  15. }@H_403_2@
  16. ErrorMsg="theinputcan'tbeempty."@H_403_2@;@H_403_2@@H_403_2@
  17. return@H_403_2@@H_403_2@false@H_403_2@;@H_403_2@@H_403_2@
  18. }@H_403_2@
  19. private@H_403_2@@H_403_2@bool@H_403_2@IsFourdigits(@H_403_2@string@H_403_2@input)@H_403_2@@H_403_2@
  20. {@H_403_2@
  21. if@H_403_2@(input.Length==4)@H_403_2@@H_403_2@
  22. {@H_403_2@
  23. return@H_403_2@@H_403_2@true@H_403_2@;@H_403_2@@H_403_2@
  24. }@H_403_2@
  25. ErrorMsg="theinputmustbefourdigits."@H_403_2@;@H_403_2@@H_403_2@
  26. return@H_403_2@@H_403_2@false@H_403_2@;@H_403_2@@H_403_2@
  27. }@H_403_2@
  28. private@H_403_2@@H_403_2@bool@H_403_2@IsDigital(@H_403_2@string@H_403_2@input)@H_403_2@@H_403_2@
  29. {@H_403_2@
  30. varregex=new@H_403_2@Regex(@@H_403_2@"^[0-9]*$"@H_403_2@);@H_403_2@@H_403_2@
  31. if@H_403_2@(regex.IsMatch(input))@H_403_2@@H_403_2@
  32. {@H_403_2@
  33. return@H_403_2@@H_403_2@true@H_403_2@;@H_403_2@@H_403_2@
  34. }@H_403_2@
  35. ErrorMsg="theinputmustbefullydigital."@H_403_2@;@H_403_2@@H_403_2@
  36. return@H_403_2@@H_403_2@false@H_403_2@;@H_403_2@@H_403_2@
  37. }@H_403_2@
  38. private@H_403_2@@H_403_2@bool@H_403_2@IsRepeat(@H_403_2@string@H_403_2@input)@H_403_2@@H_403_2@
  39. {@H_403_2@
  40. if@H_403_2@(input.Distinct().Count()==4)@H_403_2@@H_403_2@
  41. {@H_403_2@
  42. return@H_403_2@@H_403_2@true@H_403_2@;@H_403_2@@H_403_2@
  43. }@H_403_2@
  44. ErrorMsg="theinputfigurescan'tcontainduplicate."@H_403_2@;@H_403_2@@H_403_2@
  45. return@H_403_2@@H_403_2@false@H_403_2@;@H_403_2@@H_403_2@
  46. }@H_403_2@
  47. }@H_403_2@

为了确保重构正确。重构之后一定要把所有的CASE在跑一遍,确定所有的都PASS。

To-Do-List:
猜测数字@H_403_2@
输入验证@H_403_2@
生成答案
输入次数
输出猜测结果

验证搞定了。我们来整整随机数。

分析需求:产品代码需要一个随机生成的答案。(1)不重复。(2)4位(3)数字。

这里有个问题:大家都知道随机数是个概率的问题。因为每次生成的数字都不一样。看看之前Guesser类的代码

[csharp] view plain copy
  1. public@H_403_2@@H_403_2@class@H_403_2@Guesser@H_403_2@@H_403_2@
  2. {@H_403_2@
  3. private@H_403_2@@H_403_2@const@H_403_2@@H_403_2@string@H_403_2@AnswerNumber=@H_403_2@"2975"@H_403_2@;@H_403_2@@H_403_2@
  4. public@H_403_2@@H_403_2@string@H_403_2@Guess(@H_403_2@string@H_403_2@inputNumber)@H_403_2@@H_403_2@
  5. {@H_403_2@
  6. varaCount=0;@H_403_2@
  7. varbCount=0;@H_403_2@
  8. for@H_403_2@(varindex=0;index<AnswerNumber.Length;index++)@H_403_2@@H_403_2@
  9. {@H_403_2@
  10. if@H_403_2@(AnswerNumber[index]==inputNumber[index])@H_403_2@@H_403_2@
  11. {@H_403_2@
  12. aCount++;@H_403_2@
  13. continue@H_403_2@;@H_403_2@@H_403_2@
  14. }@H_403_2@
  15. if@H_403_2@(AnswerNumber.Contains(inputNumber[index].ToString()))@H_403_2@@H_403_2@
  16. {@H_403_2@
  17. bCount++;@H_403_2@
  18. }@H_403_2@
  19. }@H_403_2@
  20. return@H_403_2@@H_403_2@string@H_403_2@.Format(@H_403_2@"{0}a{1}b"@H_403_2@,aCount,bCount);@H_403_2@@H_403_2@
  21. }@H_403_2@
  22. }@H_403_2@

这里我们如果把private const string AnswerNumber = "2975";改为随机的话,那Guesser类测试的结果是不能确定的。也就是说测试依赖了一些可变的东西。比如:随机数、时间等等。

遇到这种情况应该怎么办呢?一种随机数是给产品代码用,我们可以MOCK另外一种"固定随机数"(但是要满足生成随机数的条件)来给测试用。

还是一样先写测试。

[csharp] view plain copy
  1. [TestClass]@H_403_2@@H_403_2@
  2. public@H_403_2@@H_403_2@class@H_403_2@AnswerGeneratorTest@H_403_2@@H_403_2@
  3. {@H_403_2@
  4. [TestMethod]@H_403_2@
  5. public@H_403_2@@H_403_2@void@H_403_2@should_pass_when_answer_generator_number_is_four_digits_and_fully_digital()@H_403_2@@H_403_2@
  6. {@H_403_2@
  7. Regexregex=new@H_403_2@Regex(@@H_403_2@"^\d{4}$"@H_403_2@);@H_403_2@@H_403_2@
  8. varanswerGenerator=new@H_403_2@AnswerGenerator();@H_403_2@@H_403_2@
  9. varactual=regex.IsMatch(answerGenerator.Generate());@H_403_2@
  10. Assert.AreEqual(true@H_403_2@,actual);@H_403_2@@H_403_2@
  11. }@H_403_2@
  12. @H_403_2@
  13. [TestMethod]@H_403_2@
  14. public@H_403_2@@H_403_2@void@H_403_2@should_pass_when_answer_generator_number_do_not_repeat()@H_403_2@@H_403_2@
  15. {@H_403_2@
  16. varanswerGenerator=new@H_403_2@AnswerGenerator();@H_403_2@@H_403_2@
  17. varactual=answerGenerator.Generate().Distinct().Count()==4;@H_403_2@
  18. Assert.AreEqual(true@H_403_2@,actual);@H_403_2@@H_403_2@
  19. }@H_403_2@
  20. }@H_403_2@

实现AnswerGenerator类让测试通过。

引用cao大一段代码稍加修改

[csharp] view plain copy
  1. public@H_403_2@@H_403_2@class@H_403_2@AnswerGenerator@H_403_2@@H_403_2@
  2. {@H_403_2@
  3. public@H_403_2@@H_403_2@string@H_403_2@Generate()@H_403_2@@H_403_2@
  4. {@H_403_2@
  5. varanswerNumber=new@H_403_2@StringBuilder();@H_403_2@@H_403_2@
  6. Enumerable.Range(0,9)@H_403_2@
  7. .Select(x=>new@H_403_2@{v=x,k=Guid.NewGuid().ToString()})@H_403_2@@H_403_2@
  8. .OrderBy(x=>x.k)@H_403_2@
  9. .Select(x=>x.v)@H_403_2@
  10. .Take(4).ToList()@H_403_2@
  11. .ForEach(num=>answerNumber.Append(num.ToString()));@H_403_2@
  12. return@H_403_2@answerNumber.ToString();@H_403_2@@H_403_2@
  13. }@H_403_2@
  14. }@H_403_2@

运行测试。

为了解决测试依赖可变的问题。定义IAnswerGenerator。让两种随机数类继承。

[csharp] view plain copy
  1. public@H_403_2@@H_403_2@interface@H_403_2@IAnswerGenerator@H_403_2@@H_403_2@
  2. {@H_403_2@
  3. string@H_403_2@Generate();@H_403_2@@H_403_2@
  4. }@H_403_2@

[csharp] view plain copy
  1. public@H_403_2@@H_403_2@class@H_403_2@AnswerGenerator:IAnswerGenerator@H_403_2@@H_403_2@
  2. {@H_403_2@
  3. public@H_403_2@@H_403_2@string@H_403_2@Generate()@H_403_2@@H_403_2@
  4. {@H_403_2@
  5. varanswerNumber=new@H_403_2@StringBuilder();@H_403_2@@H_403_2@
  6. Enumerable.Range(0,k=Guid.NewGuid().ToString()})@H_403_2@@H_403_2@
  7. .OrderBy(x=>x.k)@H_403_2@
  8. .Select(x=>x.v)@H_403_2@
  9. .Take(4).ToList()@H_403_2@
  10. .ForEach(num=>answerNumber.Append(num.ToString()));@H_403_2@
  11. return@H_403_2@answerNumber.ToString();@H_403_2@@H_403_2@
  12. }@H_403_2@
  13. }@H_403_2@

[csharp] view plain copy
  1. public@H_403_2@@H_403_2@class@H_403_2@AnswerGeneratorForTest:IAnswerGenerator@H_403_2@@H_403_2@
  2. {@H_403_2@
  3. public@H_403_2@@H_403_2@string@H_403_2@Generate()@H_403_2@@H_403_2@
  4. {@H_403_2@
  5. return@H_403_2@@H_403_2@"2975"@H_403_2@;@H_403_2@@H_403_2@
  6. }@H_403_2@
  7. }@H_403_2@

AnswerGenerator给产品代码用。AnswerGeneratorForTest给测试代码用。这样就可以避免测试依赖可变的问题。

相应的给Guesser类以及测试代码做个修改

[csharp] view plain copy
  1. public@H_403_2@@H_403_2@class@H_403_2@Guesser@H_403_2@@H_403_2@
  2. {@H_403_2@
  3. public@H_403_2@@H_403_2@string@H_403_2@AnswerNumber{@H_403_2@get@H_403_2@;@H_403_2@private@H_403_2@@H_403_2@set@H_403_2@;}@H_403_2@@H_403_2@
  4. @H_403_2@
  5. public@H_403_2@Guesser(IAnswerGeneratorgenerator)@H_403_2@@H_403_2@
  6. {@H_403_2@
  7. AnswerNumber=generator.Generate();@H_403_2@
  8. }@H_403_2@
  9. public@H_403_2@@H_403_2@string@H_403_2@Guess(@H_403_2@string@H_403_2@inputNumber)@H_403_2@@H_403_2@
  10. {@H_403_2@
  11. ...@H_403_2@
  12. }@H_403_2@
  13. }@H_403_2@

[csharp] view plain copy
  1. [TestClass]@H_403_2@@H_403_2@
  2. public@H_403_2@@H_403_2@class@H_403_2@GuesserTest@H_403_2@@H_403_2@
  3. {@H_403_2@
  4. private@H_403_2@Guesserguesser;@H_403_2@@H_403_2@
  5. [TestInitialize]@H_403_2@
  6. public@H_403_2@@H_403_2@void@H_403_2@Init()@H_403_2@@H_403_2@
  7. {@H_403_2@
  8. guesser=new@H_403_2@Guesser(@H_403_2@new@H_403_2@AnswerGeneratorForTest());@H_403_2@@H_403_2@
  9. }@H_403_2@
  10. ...@H_403_2@
  11. }@H_403_2@

这样我在测试代码当中就会给一个不可变的随机数。AnswerGeneratorForTest。所以之前的Guesser测试代码也不会因为每次的随机数不一样导致挂掉。

产品代码呢?直接丢AnswerGenerator进去就妥妥地。

To-Do-List:
猜测数字@H_403_2@
输入验证@H_403_2@
生成答案@H_403_2@
输入次数
输出猜测结果

OK。今天的收获。

(1)小步前进:依靠自身情况决定“小步”应该有多大。

(2)重构:之前的测试是我们重构的保障。

(3)测试依赖:测试不应该依赖于一些可变的东西。

都到这了,有没有点TDD的感觉。知道TDD的步骤了吗?

(1)新增一个测试。

(2)运行所有的测试程序并失败。

(3)做一些小小的改动。

(4)运行所有的测试,并且全部通过。

(5)重构代码以消除重复设计,优化设计。

(6)重复上面的工作。实现1~5小范围迭代。直到满足今天的Story。

上一篇还有个遗留的问题。我把它记在小本上。

(完)

猜你在找的设计模式相关文章