这是一个tdd练习题目,本文记录了本次kata的思考和练习过程。
描述
你的邻居每年都能在度假屋装饰大赛中打败你,你觉得很没面子。现在,你决定搞一个1000 x 1000的灯光网格(light grid)打败他。由于今年你表现的特别棒,圣诞老人给你发了关于如何配置灯光的说明。网格中的灯光在每个方向上的编号为0到999。每个角的坐标为(0,0),(0,999),(999,999)和(999,0)。说明中包括对给出的坐标对(coordinate pairs)范围内,进行打开(trun on)、关闭(turn off)和切换(toggle)的操作。每个坐标对表示了一个矩形的两个对角,像(0,0)到(2,2)这样的坐标对表示的是一个3x3的正方形中的9盏灯。所有的灯开始的时候是关闭的。为了将你的邻居按在地上摩擦,你需要做的就是按照圣诞老人给的说明来设置灯光。
- 对(0,0)到(999,999)做打开操作点亮所有的灯。
- 对(0,0)到(999,0)做切换操作将影响第一行的灯,关闭的灯打开 ,打开的灯关闭。
- 对(499,499)到(500,500)做关闭操作,关闭中间的4盏灯。
按照说明操作后,有多少灯是点亮的?
经过一阵忙活,你终于完成了。但是。。。你发现你貌似误解了圣诞老人的意思。你购买的灯光网格实际上有单独的亮度(brightness)控制。每盏灯都有0或更高的亮度值,亮度从0开始。
- 打开操作实际上是将亮度增加1
- 关闭操作实际上是将亮度减少1,最小值是0
- 切换操作实际上是将 亮度增加2
经过以上操作后灯光的总亮度是多少?
比如:
- 对(0,0)到(0,0)做打开操作后总亮度为1
- 对(0,0)到(999,999)做切换操作后总亮度为2000000
计划列表
动手之前需要列出完成这个灯光网格需要做哪些事,先列出要做的任务:
- 打开所有的灯
- 对第一行的灯做切换操作,效果是只点亮第一行的灯
- 对(499,499)到(500,500)做关闭操作,效果是关闭中间的4盏灯
开始写程序
第一个测试
没有测试代码之前不要写任何生产代码,所以第一个测试就是创建灯光网格(LightGrid)。
做法:创建LightGridTest,并写出创建LightGrid类的代码,这样就得到了第一个测试。
现在只要让这个测试通过就可以了,为了让测试通过,只需要创建一个LightGrid类就可以了。
然后我们就得到了LightGrid类。
现在第一个测试就完成了,运行测试,测试通过。
第二个测试
这个测试中我们要完成任务列表中的第一个任务,先看一下任务列表:
- 打开所有的灯
- 对第一行的灯做切换操作,效果是只点亮第一行的灯
- 对(499,499)到(500,500)做关闭操作,效果是关闭中间的4盏灯
现在开始做第一个任务,打开所有的灯。
根据上面的描述,写出这样一个测试:
现在主要任务是尽快让这个测试通过,先创建CoordinatePair类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E57EFxvm-1575199433431)(https://ynfeng-blog.oss-cn-beijing.aliyuncs.com/carbon (1)].png)
只写可以通过测试的代码,不要写额外的功能
为LightGrid创建trunOn方法
为LightGrid创建getLight方法
现在,还需要再创建一个Light类,并且给Light类创建一个isOn方法
目前可以通过编译了,运行测试
测试失败了,因为Light.isOn()方法返回了false,为了让测试通过,只需要将isOn()方法改成ture即可
运行测试,测试通过。
列表中的第一个任务完成了,现在可以将其划掉了
打开所有的灯- 对第一行的灯做切换操作,效果是只点亮第一行的灯
- 对(499,499)到(500,500)做关闭操作,效果是关闭中间的4盏灯
第三个测试
对第一行的灯做切换操作,效果是只点亮第一行的灯
老规矩,先写测试。
为了让编译通过,需要给LightGrid创建toggle方法
运行测试,失败了
现在,想办法让这个测试通过。LightGrid现在为止只有一个骨架,还没有任何实质上的功能实现。先思考一下,LightGrid里面应该有很多Light,这些Light应该由一个二维数组表示。
进行到这里,我们需要对已经存在的代码进行重构,以方便添加新的功能。所以,我们先将第三个测试相关的代码注释掉,专注的进行重构。
LightGrid的toggle方法也注释掉。
运行测试,测试通过,我们没有破坏任何功能。Light类我们需要实现真实的行为。
运行测试,失败了。
失败的原因是,在LightGrid中,我们还没有对Light的trunOn方法进行调用,所以我们来搞定这件事。对turnOn方法的调用,我们需要接照给出的坐标对,对范围的所有Light进行trunOn操作就可以了。
因为实现这个方法,还驱动出了CoordinatePair的新方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DT2grTSq-1575199433453)(https://ynfeng-blog.oss-cn-beijing.aliyuncs.com/carbon (2)].png)
LightGrid的getLight()也需要修改实现了
运行测试,测试通过!
现在重构完毕了,打开刚才注释掉的和toggle有关的代码,现在开始实现toggle。toggle和turnOn类似,只需要遍历坐标对范围内的Light进行toggle操作就可以了。LightGrid里我们只需复制turnOn的代码,然后修改一下就好了。
有人可能会对复制代码的行为嗤之以鼻,没关系,我们一会儿会重构它,现在先专注实现功能。给Light添加toggle方法。根据描述,toggle的意思是,打开的灯关闭,关闭的灯打开。
我们又驱动出了一个Light的turnOff方法 : )
完成了,运行测试,通过!
列表中的第二个任务完成了。
打开所有的灯对第一行的灯做切换操作,效果是只点亮第一行的灯- 对(499,499)到(500,500)做关闭操作,效果是关闭中间的4盏灯
再次重构
现在完成了两个任务了,离目标只差一步了。目前的代码中已经出现了一些重复代码,所以现在我们暂时停下前进的脚步进行重构,重构完成后我们再继续。
观察一下LightGrid类。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SNbPwLn5-1575199433461)(https://ynfeng-blog.oss-cn-beijing.aliyuncs.com/carbon (3)].png)
trunOn和toggle里面有重复代码,我想我可以把遍历LightGrid中所有Light的方法抽出到一个eachLight方法中。
运行测试,不出所料,测试通过。
修改turnOn,测试。
修改toggle,测试。
最终效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RZh8dPuP-1575199433468)(https://ynfeng-blog.oss-cn-beijing.aliyuncs.com/carbon (4)].png)
测试代码也是代码,以后也需要修改和添加新测试。所以,再来看一下测试代码。
- LigthGrid的创建,每个单元测试都需要创建,这里可以用JUnit的@setup方法,把LightGrid的创建集中在此。
- should_create_light_grid()方法现在看起来有点多余了,删除之。
- 有关遍历Light的方法可以用LightGrid.eachLight()替代。经过这样的修改后,LightGrid.getLight()方法不再被外部引用了,可以接访问级别改成private。
最终效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YEJZ7Tii-1575199433468)(https://ynfeng-blog.oss-cn-beijing.aliyuncs.com/carbon (5)].png)
最后一个任务
对(499,499)到(500,500)做关闭操作,效果是关闭中间的4盏灯
先写测试:
这里就有个问题了,只验证中间的4盏灯是否能保证正确性?我感觉不能,如果保证正确,需要验证其它5个矩形区域的灯的关着的。如图:
完善一下测试:
turnOff实现:
运行测试,测试通过!
将最后一个任务划掉
打开所有的灯对第一行的灯做切换操作,效果是只点亮第一行的灯对(499,499)到(500,500)做关闭操作,效果是关闭中间的4盏灯
补充测试用例
到这里,第一部分就完成了。但是描述中还有一个问题没有解决,就是“按照说明操作后,有多少灯是点亮的?”再添加一个任务。
打开所有的灯对第一行的灯做切换操作,效果是只点亮第一行的灯对(499,499)到(500,500)做关闭操作,效果是关闭中间的4盏灯- 打开所有灯;对第一行做切换操作;关闭中间的4盏灯;应该点亮998996盏灯。
写测试:
运行测试,测试通过。
任务列表状态:
打开所有的灯对第一行的灯做切换操作,效果是只点亮第一行的灯对(499,499)到(500,500)做关闭操作,效果是关闭中间的4盏灯打开所有灯;对第一行做切换操作;关闭中间的4盏灯;应该点亮998996盏灯
第二部分
根据描述,灯是有亮度的,而且之前所说的turnOn,turnOff,toggle等操作的含义也完全不一样了。经过分析只大部分的变化都在Light中,先改造Light类使之复合需求。
因为对灯的操作意义的变化,之前的should_998996_lights_on_after_instructions测试就不再有意义 了,所以将其删除。
之前是用一个布尔值来表示灯的开闭状态的,现在应该把内部状态改成一个亮度值。还需要添加一个获取亮度的方法,先列出任务:
- 改造Light类,内部状态改为用亮度(brightness)表示
- 对(0,0)到(0,0)做打开操作后总亮度为1
- 对(0,0)到(999,999)做切换操作后总亮度为2000000
先改造Light类
改造Light类,内部状态改为用亮度(brightness)表示- 对(0,0)到(0,0)做打开操作后总亮度为1
- 对(0,0)到(999,999)做切换操作后总亮度为2000000
对任务二添加一个测试:
给Light类添加brightness方法
运行测试,通过!
改造Light类,内部状态改为用亮度(brightness)表示- ~
对(0,0)到(0,0)做打开操作后总亮度为1 - 对(0,0)到(999,999)做切换操作后总亮度为2000000
对任务三添加一个测试
运行测试,通过!
改造Light类,内部状态改为用亮度(brightness)表示- ~
对(0,0)到(0,0)做打开操作后总亮度为1 对(0,0)到(999,999)做切换操作后总亮度为2000000
打完收工。PS:还没做参数的边界检查