演化架构与紧急设计: 测试驱动设计,第 1 部分
2009-11-05 00:00:00 来源:WEB开发网把程序划分为多个可执行一项可识别任务的方法。
把方法中的所有操作保持在同一个抽象级别
这将自然而然地得到拥有许多小方法的程序,每个小方法都只有几行代码。
组合方法是 TDD 提倡的有益设计特性之一,而我已经在 清单 11 的 getFactors() 方法中明显违反了这种模式。我可以通过执行以下步骤来修正:
将 factors 提升为内部状态。
将 factors 的初始化代码移到构造函数中。
去掉对 int[] 代码的转换,等到它变得有益时再处理它。
添加 addFactors() 的另一项测试。
第四步非常微妙但是很重要。编写出这个有缺陷的代码版本揭示出分解的第一步并不完整。隐藏在这个长方法中间的 addFactors() 代码行是可测试的行为。它是如此地微不足道,以至于在第一次查看问题时我都没有注意到它,但是现在我看到了。这是经常出现的情况。一个测试可以指引您进一步将问题分解为越来越小的块,每个块都是可以测试的。
我将暂停处理 getFactors() 的比较大的问题,而处理我新遇到的小问题。因此,我的下一个测试是 addFactors(),如清单 12 中所示:
清单 12. 测试 addFactors()@Test public void add_factors() {
Classifier3 c = new Classifier3(6);
c.addFactor(2);
c.addFactor(3);
assertThat(c.getFactors(), is(Arrays.asList(1, 2, 3, 6)));
}
清单 13 所示的测试中的代码本身十分简单:
清单 13. 添加因子的简单代码public void addFactor(int factor) {
_factors.add(factor);
}
我运行我的单元测试,充满信心地认为我会看到表示测试成功的绿条,但是却失败了!这样一个简单的测试怎么会失败?根本原因显示在图 2 中:
图 2. 测试失败的根本原因
我期望看到的列表有 1, 2, 3, 6 几个值,而实际返回的是 1, 6, 2, 3。那是因为我将代码改为在构造函数中添加 1 和数字本身。这个问题的一种解决方案是,始终在假定应先添加 1 和该数字的情况下编写期望的代码。但是这是正确的 解决方案吗?不是。问题更为基础。因子是不是一个数字列表?不是,它们是一个数字集合。我的第一个(错误)假定导致我使用一列整数作为因子,但是这是个糟糕的抽象。通过将我的代码重构为使用集合而非列表,我不但解决了这个问题,而且优化了整个解决方案,因为我现在使用的是更精确的抽象。
如果在让代码影响您的判断力之前编写测试,这正是测试可以揭露的有缺陷的思维方式。现在,由于这项简单的测试,我编写的代码的整体设计更好了,因为我已经发现了更合适的抽象。
结束语
到目前为止,我以处理完全数为背景讨论了紧急设计。特别是,注意第一版的解决方案(后测试版本)对数据类型做出了同样有缺陷的假设。“后测试” 将测试代码的粗糙功能,而非各个部分。TDD 将测试构成粗糙功能的构建块,在测试过程中揭露更多信息。
在下一期文章中,我将继续讨论完全数问题,演示在执行测试时形成的各种设计的更多示例。在我完成 TDD 版本时,我将比较一下两个代码库。我还将解答其他某些棘手的 TDD 设计问题,例如是否测试及何时测试私有方法。
更多精彩
赞助商链接