测试驱动开发TDD三

大家好:

上一篇我剩下的To-Do-List:

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

今天争取全部搞定。

现在我们Guesser、生成答案、输入验证都有了。把它们组装成一起摇身一变成一个Game!

用一个类把这些职责单一的小模块组合起来。我暂且称它为GameManager.

分析剩下的需求。(1)输入6次GameOver.(2)输入合法数字返回猜测结果。(3)游戏结束提示重新开始游戏。(4)中途输入exit退出游戏。(5)输入正确答案,GameOver。

先把之前写的Guesser提出一个接口。

[csharp] view plain copy
  1. publicinterfaceIGuesser
  2. {
  3. stringAnswerNumber{get;}
  4. stringGuess(stringinputNumber);
  5. }


[csharp] view plain copy
  1. publicclassGuesser:IGuesser
  2. {
  3. publicstringAnswerNumber{get;privateset;}
  4. publicGuesser(IAnswerGeneratorgenerator)
  5. {
  6. AnswerNumber=generator.Generate();
  7. }
  8. publicstringGuess(stringinputNumber)
  9. {
  10. ...
  11. }
  12. }


TestFirst.

新建GameManagerTest

[csharp] view plain copy
  1. [TestClass]
  2. publicclassGameManagerTest
  3. {
  4. [TestMethod]
  5. publicvoidshould_return_game_over_when_input_times_is_six_and_result_is_wrong()
  6. {
  7. IGuesserguess=newGuesser(newAnswerGeneratorForTest());
  8. vargame=newGameManager(guess);
  9. varinput="1368";
  10. varmaxtimes=6;
  11. varactual=false;
  12. for(vartime=0;time<maxtimes;time++)
  13. {
  14. game.Guess(input);
  15. }
  16. actual=game.IsGameOver;
  17. Assert.AreEqual(true,actual);
  18. }
  19. }


实现GameManager让测试通过。

[csharp] view plain copy
  1. publicclassGameManager
  2. {
  3. privatereadonlyIGuesserguesser;
  4. publicboolIsGameOver{get;privateset;}
  5. privateconstintMAX_TIMES=6;
  6. privateinttimes;
  7. publicGameManager(IGuesserguesser)
  8. {
  9. this.guesser=guesser;
  10. }
  11. publicvoidGuess(stringinput)
  12. {
  13. times++;
  14. IsGameOver=times==MAX_TIMES;
  15. guesser.Guess(input);
  16. }
  17. }

OK

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

输入猜测结果。这里包含一个猜对的情况下应该返回"you win"并且Gameover。其他输入返回的结果,在Guesser和validator中已经Cover了。挑几个来测试一下输入输出。

[csharp] view plain copy
  1. [TestClass]
  2. publicclassGameManagerTest
  3. {
  4. private_gameManager_game;
  5. [TestInitialize]
  6. publicvoidInit()
  7. {
  8. IGuesserguesser=newGuesser(newAnswerGeneratorForTest());
  9. _game=new_gameManager(guesser);
  10. }
  11. [TestMethod]
  12. publicvoidshould_return__game_over_when_input_times_is_six_and_result_is_wrong()
  13. {
  14. conststringinput="1368";
  15. constintmaxtimes=6;
  16. varactual=false;
  17. for(vartime=0;time<maxtimes;time++)
  18. {
  19. _game.Guess(input);
  20. }
  21. actual=_game.Is_gameOver;
  22. Assert.AreEqual(true,actual);
  23. }
  24. [TestMethod]
  25. publicvoidshould_return_you_win_and__game_is_over_when_input_is_equal_answer_number()
  26. {
  27. conststringinput="2975";
  28. varactual=_game.Guess(input);
  29. Assert.AreEqual("Youwin!",actual);
  30. Assert.AreEqual(true,_game.Is_gameOver);
  31. }
  32. [TestMethod]
  33. publicvoidshould_return_try_again_input_must_be_four_digits_when_input_is_not_equal_four_digits()
  34. {
  35. conststringinput="15243";
  36. varactual=_game.Guess(input);
  37. Assert.AreEqual("tryagaintheinputmustbefourdigits.",actual);
  38. }
  39. [TestMethod]
  40. publicvoidshould_return_try_again_input_can_not_be_empty_when_input_is_empty()
  41. {
  42. conststringinput="";
  43. varactual=_game.Guess(input);
  44. Assert.AreEqual("tryagaintheinputcan'tbeempty.",actual);
  45. }
  46. [TestMethod]
  47. publicvoidshould_return_try_again_input_must_be_fully_digital_when_input_is_not_all_digital()
  48. {
  49. conststringinput="a4sw";
  50. varactual=_game.Guess(input);
  51. Assert.AreEqual("tryagaintheinputmustbefullydigital.",actual);
  52. }
  53. }

修改GameManager类,让所有CASEPASS.

[csharp] view plain copy
  1. publicstringGuess(stringinput)
  2. {
  3. times++;
  4. IsGameOver=times==MAX_TIMES;
  5. varvalidator=newValidator();
  6. if(!validator.Validate(input))
  7. {
  8. return"tryagain"+validator.ErrorMsg;
  9. }
  10. varresult=guesser.Guess(input);
  11. if(result=="4a0b")
  12. {
  13. IsGameOver=true;
  14. return"Youwin!";
  15. }
  16. return"tryagainresultis"+result+".";
  17. }

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

最后:完善GameManager类的work flow。

[csharp] view plain copy
  1. publicclassGameManager
  2. {
  3. privateconstintMAX_TIMES=6;
  4. privateinttimes;
  5. privatereadonlyIGuesserguesser;
  6. publicboolIsGameOver{get;privateset;}
  7. publicGameManager(IGuesserguesser)
  8. {
  9. this.guesser=guesser;
  10. }
  11. privatevoidStart()
  12. {
  13. times=0;
  14. IsGameOver=false;
  15. OutputGameHeader();
  16. }
  17. publicvoidRun()
  18. {
  19. Start();
  20. while(!IsGameOver)
  21. {
  22. Console.WriteLine();
  23. Console.WriteLine(string.Format("[The{0}sttime]:pleaseinputnumber!",times+1));
  24. varinput=Console.ReadLine();
  25. if(IsExit(input))continue;
  26. varresult=Guess(input);
  27. Console.WriteLine(result);
  28. }
  29. OutputGamefooter();
  30. }
  31. privateboolIsExit(stringinput)
  32. {
  33. if(input.ToLower().Trim()=="exit")
  34. {
  35. Console.WriteLine("Makesuretoexitgame?(Y/N)");
  36. varreadLine=Console.ReadLine();
  37. if(readLine!=null)
  38. {
  39. varisexit=readLine.ToLower().Trim();
  40. if(isexit=="y")
  41. {
  42. IsGameOver=true;
  43. }
  44. }
  45. returntrue;
  46. }
  47. returnfalse;
  48. }
  49. publicstringGuess(stringinput)
  50. {
  51. times++;
  52. IsGameOver=times==MAX_TIMES;
  53. varvalidator=newValidator();
  54. if(!validator.Validate(input))
  55. {
  56. return"tryagain"+validator.ErrorMsg;
  57. }
  58. varresult=guesser.Guess(input);
  59. if(result=="4a0b")
  60. {
  61. IsGameOver=true;
  62. return"Youwin!";
  63. }
  64. return"tryagainresultis"+result+".";
  65. }
  66. privatevoidOutputGameHeader()
  67. {
  68. Console.Clear();
  69. Console.WriteLine("---GameStart!---");
  70. Console.WriteLine("---------------------------------------------------------------");
  71. Console.WriteLine("|Youcaninputanumberorinputexitforexitingthisgame!|");
  72. Console.WriteLine("---------------------------------------------------------------");
  73. }
  74. privatevoidOutputGamefooter()
  75. {
  76. Console.WriteLine("--------------------------------");
  77. Console.WriteLine("|GameOver![Answer]is"+guesser.AnswerNumber+"|");
  78. Console.WriteLine("--------------------------------");
  79. }
  80. }

Program.cs

[csharp] view plain copy
  1. classProgram
  2. {
  3. staticvoidMain(string[]args)
  4. {
  5. varisRepeatGame=false;
  6. do
  7. {
  8. IGuesserguesser=newGuesser(newAnswerGenerator());
  9. vargame=newGameManager(guesser);
  10. game.Run();
  11. Console.WriteLine("Tryagain?(Y/N)");
  12. varline=Console.ReadLine();
  13. if(line==null)continue;
  14. varreadLine=line.ToLower().Trim();
  15. isRepeatGame=readLine=="y";
  16. }while(isRepeatGame);
  17. }
  18. }

跑下所有的测试。

到这里。这个游戏的基本功能算做完了。做的比较简单。测试和产品代码也比较随意。

大家也可以试着做一做。感受感受测试驱动产品代码。

运行效果


下面是我在实践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的理解。也会把这些记录下来。

代码比较简单。没源码!

大家有时间可以感觉感觉TDD。如果有什么疑问、或者觉得它有什么令你不爽的地方。给我发邮件或者留言http://my.csdn.net/wxr0323

最后 谢zynx蒂姆&YUHENG.

(完)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结