认识了TDD,我们以实际案例过程来更好的学习TDD。

案例需求

保龄球单局积分规则为:
1、保龄球按顺序每轮允许投2个球,投完10轮为1局。
2、每击倒1个瓶得1分。投完一轮将两个球的“所得分”相加,为该轮的“应得分”,10轮依次累计为全局的总分。
3、保龄球运动有统一格式的记分表。第一球将全部瓶击倒时,称为“全中”。该轮所得分为10分。第二球不得再投。但按规则规定, 应奖励下轮两个球的所得分。它们所得分之和为该轮的应得 分。
4、当第一球击倒部分瓶时,应在左边小格内记上被击倒的木 瓶数,作为第一球的所得分。如果第二球将剩余瓶全部击倒,则称为“补中”。该轮所得分亦为10分。按规则规定,应奖励下轮第一球的所得分。它们所得分 之和为该轮的应得分。
5、第10轮全中时,应在同一条球道上继续投守最后两个球结束全局。这两个球的所得分应累计在该局总分内。
6、第10轮为补中时,应在同一条球道上继续投守最后一个球结束全局。这个球的所得分应累计在该局总分内。
一局总分为300分

TDD过程

整个过程用Php实现。
阅读说明:
1、为了简洁,每部分代码实例只包含新增或修改过的函数体。
2、相对于之前新增代码,注释为”新增测试代码“或”新增实现代码“,如果注释只涉及几行,会单独说明;修改按同样方式处理。

step1 一局开始,记分是0

编写测试代码(BowlingGameRecordTest.php):

public function testGameRecord(){//初始保龄球得分为0$this->assertEquals(0,$this->BowlingGameRecord->getGameRecord());}

实现代码(BowlingGameRecord.php):

private $gameRecord ;public function __construct() {$this->gameRecord = 0;}public function getGameRecord(){return $this->gameRecord;}

step2 测试简单情况,每次投掷得1分,共投掷20次,得20分

测试代码(BowlingGameRecordTest.php):

    public function testGameRecord(){...//新增测试代码//每次得1分,共20次,得20分for($i=1;$i<=20;$i++)$this->BowlingGameRecord->strikeOne(1);$this->assertEquals(20, $this->BowlingGameRecord->getGameRecord());}

实现代码(BowlingGameRecord.php):

//新增实现代码
public function strikeOne($score){$score = intval($score);$this->gameRecord += $score;}

step3 测试全中,但为了简单,假定第一轮全中,测到第二轮投掷完即可

测试代码(BowlingGameRecordTest.php):

public function testGameRecord(){...//新增测试代码//第一次投掷全中,第2,3次投掷分别得4,5分,共得28分$this->BowlingGameRecord->newGame();$this->BowlingGameRecord->strikeOne(10);$this->BowlingGameRecord->strikeOne(4);$this->BowlingGameRecord->strikeOne(5);$this->assertEquals(28, $this->BowlingGameRecord->getGameRecord());}

实现代码(BowlingGameRecord.php):

public function strikeOne($score){$score = intval($score);//累计本次投掷得分        $this->gameRecord += $score;//新增实现代码//记录本次投掷得分$currentStrikeTimes = count($this->scorePerStrike);print $currentStrikeTimes . '\n';$this->scorePerStrike[$currentStrikeTimes] = $score;if($score == 0){//第一轮第一次全中,则第一轮下一次(实际没投掷)为0if($score == 10)$scorePerStrike[$currentStrikeTimes+1] = 0;} else {//根据是否新一轮做处理$isNewOrder = false;if($currentStrikeTimes%2 == 0)$isNewOrder = true;//如果每轮第一次全中,则每轮下一次(实际没投掷)为0if($score == 10 && $isNewOrder)$this->scorePerStrike[$currentStrikeTimes+1] = 0;if($isNewOrder){print $currentStrikeTimes;if($this->scorePerStrike[$currentStrikeTimes-2] == 10) //上轮全中$this->gameRecord += $score;  //本次分也是奖励分} else {print $currentStrikeTimes;if($this->scorePerStrike[$currentStrikeTimes-3] == 10) //上轮全中$this->gameRecord += $score;  //本次分也是奖励分}}}public function newGame(){$this->gameRecord = 0;if(isset($this->scorePerStrike))unset($this->scorePerStrike);$this->scorePerStrike = array();} //每轮每次投中得分,每两个为1轮,如果每轮第一次全中,则每轮下一次(实际没投掷)为0分private    $scorePerStrike ;

step4 测试补中,但为了简单,假定第一轮是补中,测到第二轮投掷完即可

测试代码(BowlingGameRecordTest.php):

public function testGameRecord(){...//新增测试代码//第1,2,3,4次投掷分别得5,5,6,3分(第一轮补中),共得25分$this->BowlingGameRecord->newGame();$this->BowlingGameRecord->strikeOne(5);$this->BowlingGameRecord->strikeOne(5);$this->BowlingGameRecord->strikeOne(6);$this->BowlingGameRecord->strikeOne(3);$this->assertEquals(25, $this->BowlingGameRecord->getGameRecord());
}

实现代码(BowlingGameRecord.php):

public function strikeOne($score){$score = intval($score);//累计本次投掷得分        $this->gameRecord += $score;//记录本次投掷得分$currentStrikeTimes = count($this->scorePerStrike);
//        print $currentStrikeTimes . '\n';$this->scorePerStrike[$currentStrikeTimes] = $score;if($score == 0){//第一轮第一次全中,则第一轮下一次(实际没投掷)为0if($score == 10)$scorePerStrike[$currentStrikeTimes+1] = 0;} else {//根据是否新一轮做处理$isNewOrder = false;if($currentStrikeTimes%2 == 0)$isNewOrder = true;//如果每轮第一次全中,则每轮下一次(实际没投掷)为0if($score == 10 && $isNewOrder)$this->scorePerStrike[$currentStrikeTimes+1] = 0;if($isNewOrder){if($this->scorePerStrike[$currentStrikeTimes-2] == 10) //上轮全中$this->gameRecord += $score;  //本次分也是奖励分//新增实现代码:下面4行else {if($this->scorePerStrike[$currentStrikeTimes-2] + $this->scorePerStrike[$currentStrikeTimes-1]  == 10) //上轮补中$this->gameRecord += $score;  //本次分也是奖励分}} else {if($this->scorePerStrike[$currentStrikeTimes-3] == 10) //上轮全中$this->gameRecord += $score;  //本次分也是奖励分}}

step5 测试特殊情况,满分300分情况

测试代码(BowlingGameRecordTest.php):

 public function testGameRecord(){...//新增测试代码//所有全中,共得300分$this->BowlingGameRecord->newGame();for($i=1;$i<=12;$i++)$this->BowlingGameRecord->strikeOne(10);$this->assertEquals(300, $this->BowlingGameRecord->getGameRecord());
}

实现代码(BowlingGameRecord.php):

public function strikeOne($score){$score = intval($score);//修订实现代码:下面6行//记录本次投掷得分$currentStrikeTimes = count($this->scorePerStrike);$this->scorePerStrike[$currentStrikeTimes] = $score;//累计本次投掷得分(注意10轮之后只记录奖励分)if($currentStrikeTimes < 20)        $this->gameRecord += $score;if($score == 0){//第一轮第一次全中,则第一轮下一次(实际没投掷)为0if($score == 10)$scorePerStrike[$currentStrikeTimes+1] = 0;} else {//根据是否新一轮做处理$isNewOrder = false;if($currentStrikeTimes%2 == 0)$isNewOrder = true;//如果每轮第一次全中,则每轮下一次(实际没投掷)为0if($score == 10 && $isNewOrder)$this->scorePerStrike[$currentStrikeTimes+1] = 0;if($isNewOrder){//上轮全中if($this->scorePerStrike[$currentStrikeTimes-2] == 10) {//修订实现代码:下面5行if($currentStrikeTimes-2 < 20)$this->gameRecord += $score;  //本次分也是奖励分if($currentStrikeTimes-4>=0 && $this->scorePerStrike[$currentStrikeTimes-4] == 10) //上两轮全中$this->gameRecord += $score;  //本次分也是奖励分}else {if($this->scorePerStrike[$currentStrikeTimes-2] + $this->scorePerStrike[$currentStrikeTimes-1]  == 10) //上轮补中$this->gameRecord += $score;  //本次分也是奖励分}} else {if($this->scorePerStrike[$currentStrikeTimes-3] == 10) //上轮全中$this->gameRecord += $score;  //本次分也是奖励分}}}

step6 测试一个正常投掷情况

测试代码(BowlingGameRecordTest.php):

public function testGameRecord(){...//新增测试代码//随机一局:6,3 10 10 10 5,2 8,2 8,1 9,0 10 5,5 2,共得156分$this->BowlingGameRecord->newGame();$this->BowlingGameRecord->strikeOne(6);$this->BowlingGameRecord->strikeOne(3);$this->BowlingGameRecord->strikeOne(10);$this->BowlingGameRecord->strikeOne(10);$this->BowlingGameRecord->strikeOne(10);$this->BowlingGameRecord->strikeOne(5);$this->BowlingGameRecord->strikeOne(2);$this->BowlingGameRecord->strikeOne(8);$this->BowlingGameRecord->strikeOne(2);$this->BowlingGameRecord->strikeOne(8);$this->BowlingGameRecord->strikeOne(1);$this->BowlingGameRecord->strikeOne(9);$this->BowlingGameRecord->strikeOne(0);$this->BowlingGameRecord->strikeOne(10);$this->BowlingGameRecord->strikeOne(5);$this->BowlingGameRecord->strikeOne(5);$this->BowlingGameRecord->strikeOne(2);$this->assertEquals(156, $this->BowlingGameRecord->getGameRecord());
}

实现代码(BowlingGameRecord.php):
正确,不用修改

step7 重构BowlingGameRecord,重构中发现一个Bug

测试代码(BowlingGameRecordTest.php):
不用修改

实现代码(BowlingGameRecord.php):

public function strikeOne($score){$score = intval($score);//记录本次投掷得分$currentStrikeTimes = count($this->scorePerStrike);$this->scorePerStrike[$currentStrikeTimes] = $score;//(重构)修订实现代码:源代码简化如下//如果每轮第一次全中,则每轮下一次(实际没投掷)为0if($currentStrikeTimes%2 == 0 && $score == 10)$this->scorePerStrike[$currentStrikeTimes+1] = 0;//累计本次投掷得分(注意10轮之后只记录奖励分)if($currentStrikeTimes < 20)        $this->gameRecord += $score;//奖励分处理$this->rewardPerStrike($currentStrikeTimes,$score);}//(重构)新增实现代码private    function rewardPerStrike($currentStrikeTimes,$score){$currentStrikeTimes = intval($currentStrikeTimes);$score = intval($score);if($currentStrikeTimes == 0)return;//新一轮第一次投掷标识if($currentStrikeTimes%2 == 0){//上轮全中if($this->scorePerStrike[$currentStrikeTimes-2] == 10) {if($currentStrikeTimes-2 < 20)$this->gameRecord += $score;  //本次分是上一轮全中奖励分if($currentStrikeTimes-4>=0 && $this->scorePerStrike[$currentStrikeTimes-4] == 10) //上两轮全中$this->gameRecord += $score;  //本次分是上一轮的上一轮全中奖励分}else {if($this->scorePerStrike[$currentStrikeTimes-2] + $this->scorePerStrike[$currentStrikeTimes-1]  == 10) //上轮补中$this->gameRecord += $score;  //本次分是全中奖励分}} else {  //每一轮第二次投掷标识if($this->scorePerStrike[$currentStrikeTimes-3] == 10) //上轮全中$this->gameRecord += $score;  //本次分是全中奖励分}print ' $currentStrikeTimes=' . $currentStrikeTimes . ' gameRecord=' . $this->gameRecord;}

至此,用TDD开发保龄球单局记分完成。在整个开发过程中,大家可以看到每个步骤增加代码都不多,属于小步快跑;运行一旦发现之前的测试用例出错,马上进行修订,包括最后重构,都让人感觉很有把握;从效率上来说,前后总共用时2:50分钟,还算比较快的;有兴趣的同仁可以试试用自己最熟悉的方式开发,最后能编出正确的保龄球单局积分程序大约花多少时间。

实践体验总结

  1. 从简单入手,快速开始,并逐步找到解决问题的方法。
  2. 看到绿色条就开心,总觉得又前进了一步。
  3. 看到测试红色条就马上紧张,特别是之前通过的测试又不通过了,但由于步骤不大,解决问题没有很高难度,能快速解决问题。
  4. 迫使你不端重构代码,但由于有之前测试保证,重构比较大胆并放心 。
  5. 运行测试频繁,基本只要上改过代码,就运行一次,但感觉会更好。
  6. 重构过程中,可能很快会撤消刚不久前所做的工作,但并不给人做无用功的感觉,而是“原来应该这样做”的想法。
  7. 对代码有信心,无多余代码,测试覆盖率高。
  8. 在做过程中,可随需中断,之后可快速接着开始 。
  9. 相比于正常编码+测试+修改bug,感觉TDD所用时间能节省。
  10. 增加代码量还是不少,但在可接受范围内。

组织级TDD实践如何落地,见“企业如何落地TDD"。

TDD系列3-TDD过程实例-保龄球单局积分算法相关推荐

  1. BizTalk学习笔记系列之二:实例说明如何使用BizTalk

    BizTalk学习笔记系列之二:实例说明如何使用BizTalk --.BizTalk学习笔记系列之二<?XML:NAMESPACE PREFIX = O /> Aaron.Gao,2006 ...

  2. 架构的坑系列:重构过程中的过度设计

    架构的坑系列:重构过程中的过度设计 软件架构   2016-06-03 08:47:02 发布 您的评价:       5.0   收藏     2收藏 这个系列是 坑 系列,会说一些在系统设计,系统 ...

  3. 操作系统算法模拟实例之单处理机系统进程调度

    操作系统算法模拟实例之单处理机系统进程调度 1.实验目的 在多道程序或多任务系统中,系统回时处于就绪态的进程有若干个,为使系统中 的各进程能有条不素地运行,必须选择某种调度策略,以选择一进程占用处理机 ...

  4. 吴恩达机器学习系列理论加实践(二 、单变量线性回归及matlab实践)

    二.单变量线性回归 2.1模型表示 同样以之前的房屋价格预测实例开始: 通过学习算法利用训练集训练模型h,对于新输入的数据size of house就可以输出其预测值price: 如何表达这个模型h: ...

  5. SAP PM 入门系列16 - KO88对维护工单做结算

    SAP PM 入门系列16 - KO88对维护工单做结算 1, 如下的维护工单100316384, 该工单settlement rule信息, Settle type, FUL & PER , ...

  6. cadence spb 16.5 破解过程实例和使用感受_赤松子耶_新浪博客

    cadence spb 16.5 破解过程实例和使用感受_赤松子耶_新浪博客 Cadence Allegro16.5详细安装具体的步骤 1.下载SPB16.5下来后,点setup.exe,先安装第一项 ...

  7. stm32之ADC应用实例(单通道、多通道、基于DMA)

    硬件:STM32F103VCT6 开发工具:Keil uVision4 下载调试工具:ARM仿真器 网上资料很多,这里做一个详细的整合.(也不是很详细,但很通俗). 所用的芯片内嵌3个12位的模拟/数 ...

  8. ENSP配置 实例二 单臂路由配置

    ENSP配置 实例二 单臂路由配置 单臂路由配置 配置 路由: interface EigabitEthernet0/0/0 ip address 10.0.1.1 24 interface Eiga ...

  9. 浅谈嵌入式MCU软件开发之S32K1xx系列MCU启动过程及重映射代码到RAM中运行方法详解

    内容提要 注:本文摘自NXP工程师胡恩伟的微信公众号"汽车电子expert成长之路",大家感兴趣可以关注一下. 引言 1. S32K1xx系列MCU启动过程详解(startup_S ...

最新文章

  1. [概统]本科二年级 概率论与数理统计 第四讲 连续型随机变量
  2. 四层交换机是什么?有什么用?与二层/三层交换机有何区别?
  3. 在 XML 中添加实体
  4. 你们是魔鬼吗?Adobe研发了一款反PS工具:自己打自己?
  5. pytorch查缺补漏之CUDA,自动求导
  6. [过年菜谱之]清蒸鲍鱼
  7. AjaxPro实现方法
  8. linux socket 程序被ctrl+c或者异常终止,提示:bind error:Address already in use,解决办法...
  9. STM32H7B0 HAL库中关于DMA的注意事项以及DCMI调试遇到的问题及解决方法
  10. stm32f107使用外部16MHz晶振
  11. 企业数字化转型是否缺少部落知识?
  12. 【LeetCode】460 and 1132(LFU缓存机制)
  13. fluxion5.9 踩坑
  14. 部署IBM区块链平台:开发区块链业务网络到Hyperledger Fabric(多个组织)
  15. Python项目分析:预测双色球福利彩票中奖号码
  16. 验证身份证号是否正确,计算身份证号最后一位
  17. Python 处理图片
  18. 选择 DCIM 时需要注意哪些关键问题
  19. 各种效应001---乒乓效应在软件中的体现_孤岛效应
  20. 掌握这15个可视化图表,小白也能轻松玩转数据分析

热门文章

  1. Workerman 小蝌蚪聊天室去掉端口
  2. 针对新冠肺炎微博热搜话题使用R语言进行文本特征提取的四种方法(二) —— 基于TF-IDF的特征提取
  3. 别小看了手机蓝牙功能,它有5大“神奇”用途!你用过其中几种
  4. SI4702 驱动 基于STC单片机
  5. 苹果公司CEO乔布斯的一次演讲
  6. 微信“摇一摇·周边”正式开放
  7. python3.11下载和安装
  8. 【 持续更新 】Android开发笔记汇总篇,爬各种坑,敲高效代码,各种奇难杂症,有您要治的病 。
  9. DDS融合TSN:实时数据交换解决方案
  10. APP与小程序的区别以及各自的优势