徐昊 · TDD 项目实战 70 讲
徐昊
Thoughtworks 中国区 CTO
18159 人已学习
新⼈⾸单¥98
登录后,你可以任选4讲全文学习
课程目录
已完结/共 88 讲
实战项目二|RESTful开发框架:依赖注入容器 (24讲)
实战项目三|RESTful Web Services (44讲)
徐昊 · TDD 项目实战 70 讲
15
15
1.0x
00:00/00:00
登录|注册

贾静测试TDD03

你好,我是徐昊。今天我们来继续进行命令行参数解析的 TDD 演示。
首先让我们回顾一下题目与需求与代码进度。如前所述,题目源自 Bob 大叔的 Clean Code 第十四章:
我们中的大多数人都不得不时不时地解析一下命令行参数。如果我们没有一个方便的工具,那么我们就简单地处理一下传入 main 函数的字符串数组。有很多开源工具可以完成这个任务,但它们可能并不能完全满足我们的要求。所以我们再写一个吧。
 
传递给程序的参数由标志和值组成。标志应该是一个字符,前面有一个减号。每个标志都应该有零个或多个与之相关的值。例如:
 
-l -p 8080 -d /usr/logs
 
“l”(日志)没有相关的值,它是一个布尔标志,如果存在则为 true,不存在则为 false。“p”(端口)有一个整数值,“d”(目录)有一个字符串值。标志后面如果存在多个值,则该标志表示一个列表:
 
-g this is a list -d 1 2 -3 5
 
"g"表示一个字符串列表[“this”, “is”, “a”, “list”],“d"标志表示一个整数列表[1, 2, -3, 5]。
 
如果参数中没有指定某个标志,那么解析器应该指定一个默认值。例如,false 代表布尔值,0 代表数字,”"代表字符串,[]代表列表。如果给出的参数与模式不匹配,重要的是给出一个好的错误信息,准确地解释什么是错误的。
 
确保你的代码是可扩展的,即如何增加新的数值类型是直接和明显的。
目前我们的代码结构如下图所示:
我们目前的任务列表为:
ArgsTest:
// sad path:
// TODO: -bool -l t / -l t f
// TODO: - int -p/ -p 8080 8081
// TODO: - string -d/ -d /usr/logs /usr/vars
// default value:
// TODO: - bool : false
// TODO: -int :0
// TODO: - string ""

调整任务列表

当我们罗列任务列表的时候,还没有进行重构,系统中也只有 Args 一个类。而经过重构之后,我们提取了 OptionParser 接口,以及与之对应的实现类:BooleanOptionParser 和 SingleValuedOptionParser。那么当再去构造测试的时候,就存在两个不同的选择:继续针对 Args 进行测试,或是直接对 BooleanOptionParser 进行测试。
代码分别如下所示:
@Test
public void should_not_accept_extra_argument_for_boolean_option() {
TooManyArgumentsException e = assertThrows(TooManyArgumentsException.class,
() -> Args.parse(BooleanOption.class, "-l", "t"));
assertEquals("l", e.getOption());
}
@Test
public void should_not_accept_extra_argument_for_boolean_option() {
TooManyArgumentsException e = assertThrows(TooManyArgumentsException.class, () ->
new BooleanOptionParser().parse("-l", "t", option("l")));
assertEquals("l", e.getOption());
}
在当前的架构下,这两个测试是等效的功能验证,但是它们的测试范围不同,在下图中,我用虚线边框圈定了它们的范围:
那么在这种情况下,我们可以选择粒度更小的测试,这样更有益于问题的定位。于是,我们可以修改任务列表,将剩余的任务分配到对应的组件上去:
BooleanOptionParserTest:
// sad path:
// TODO: -bool -l t / -l t f
// default:
// TODO: - bool : false
SingleValuedOptionParserTest:
// sad path:
// TODO: - int -p/ -p 8080 8081
// TODO: - string -d/ -d /usr/logs /usr/vars
// default value:
// TODO: -int :0
// TODO: - string ""
现在让我们进入红 / 绿循环:
00:00 / 00:00
    1.0x
    • 2.0x
    • 1.5x
    • 1.25x
    • 1.0x
    • 0.75x
    • 0.5x
    网页全屏
    全屏
    00:00
    类似的,根据任务列表,完成 SingleValuedOptionParser 的功能:
    00:00 / 00:00
      1.0x
      • 2.0x
      • 1.5x
      • 1.25x
      • 1.0x
      • 0.75x
      • 0.5x
      网页全屏
      全屏
      00:00

      按照测试策略重组测试

      在这个红 / 绿环节中,我们发现在整数类型和字符串类型的异常场景中,差异仅仅在于如何构造 SingleValuedOptionParser:
      new SingleValuedOptionParser(0, Integer:parseInt)
      new SingleValuedOptionParser("", String::valueOf)
      也就是说,仅仅是测试代码的差别,而被测试的代码则没有任何区别。我们按照任务列表,再构造其他场景的测试,也仅仅是不同测试数据的重复而已。所以将剩余任务从列表中取消就好了。
      在当前的代码中,还遗存着一些重构前的测试。对比经过重构之后新写的测试,就会发现对于类似的功能,我们测试的出发点和测试的范围都有不同,这是一种坏味道。我们需要对测试进行重构,以消除这些不一致:
      00:00 / 00:00
        1.0x
        • 2.0x
        • 1.5x
        • 1.25x
        • 1.0x
        • 0.75x
        • 0.5x
        网页全屏
        全屏
        00:00
        在继续完成其他功能之前,我们可以快速审查一下代码,可以显而易见地发现几个明显的 Bug,那么我们可以通过一系列红 / 绿环节来修复它们:
        00:00 / 00:00
          1.0x
          • 2.0x
          • 1.5x
          • 1.25x
          • 1.0x
          • 0.75x
          • 0.5x
          网页全屏
          全屏
          00:00
          好了,到此为止,我们得到了一个颇为健壮的代码,以及清晰、可扩展的代码结构。

          小结

          在这节课中,我们展示了红 / 绿 / 重构循环是如何与任务列表互动,任务列表又是怎样持续指导我们进行测试驱动开发的。让我们回想一下最开始构想的任务列表:
          // TODO: boolean -l
          // TODO: int -p 8080
          // TODO: string -d /usr/logs
          // TODO: example 1
          // sad path:
          // TODO: -bool -l t / -l t f
          // TODO: - int -p/ -p 8080 8081
          // TODO: - string -d/ -d /usr/logs /usr/vars
          // default value:
          // TODO: - bool : false
          // TODO: -int :0
          // TODO: - string ""
          我们真正的开发过程是这样的,先按照任务列表完成了一块功能点:
          // TODO: boolean -l
          // TODO: int -p 8080
          // TODO: string -d /usr/logs
          // TODO: example 1
          发现了坏味道,开始重构。通过重构引入了新的组件,改变了架构。于是剩余的任务列表改为:
          BooleanOptionParserTest:
          // sad path:
          // TODO: -bool -l t / -l t f
          // default:
          // TODO: - bool : fals
          SingleValuedOptionParserTest:
          // sad path:
          // TODO: - int -p/ -p 8080 8081
          // default value:
          // TODO: -int :0
          陆续完成这些任务,发现不一致的测试策略,重组测试。然后进行代码审查,发现了几个缺陷,于是剩余任务列表变为(请重点关注列表的变化):
          ArgsTest:
          // TODO:无标注的参数
          // TODO:不支持的类型
          SingleValuedOptionParserTest:
          // TODO: 错误的数值格式
          不难发现,任务列表是一个随代码结构(重构)、测试策略(在哪个范围内测试)、代码实现情况(存在哪些缺陷)等因素而动态调整的列表。它的内容体现了我们最新的认知,它的变化记录了我们认知改变的过程。
          下节课,我们将继续完成命令行列表标志的功能。我们会重复任务分解与红 / 绿 / 重构循环。请注意,对于列表标志的任务分解与我们已完成的功能有何不同。

          思考题

          请根据当前代码结构,对列表标志功能进行任务分解。
          欢迎把你学习这节课的体会与思考分析在留言区,也欢迎你把课程分享给自己的同事或朋友,我们下节课再见!
          确认放弃笔记?
          放弃后所记笔记将不保留。
          新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
          批量公开的笔记不会为你同步至部落
          公开
          同步至部落
          取消
          完成
          0/2000
          荧光笔
          直线
          曲线
          笔记
          复制
          AI
          • 深入了解
          • 翻译
            • 英语
            • 中文简体
            • 中文繁体
            • 法语
            • 德语
            • 日语
            • 韩语
            • 俄语
            • 西班牙语
            • 阿拉伯语
          • 解释
          • 总结

          本文介绍了如何使用测试驱动开发(TDD)的方式来进行命令行参数解析的演示。文章首先回顾了需求和代码进度,然后讨论了如何进行任务列表的调整和重构,以及如何根据任务列表进行红/绿循环来完成功能。作者展示了如何根据任务列表完成功能点,发现坏味道后进行重构,并通过红/绿循环来修复缺陷。文章强调了任务列表的重要性,它是动态调整的,记录了认知的改变过程。最后,作者提出了一个思考题,要求读者根据当前代码结构对列表标志功能进行任务分解。整体来说,本文强调了TDD的重要性,以及如何通过任务列表和红/绿循环来指导开发过程,对读者进行了一次技术实践的指导和启发。

          仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
          《徐昊 · TDD 项目实战 70 讲》
          新⼈⾸单¥98
          立即购买
          unpreview
          登录 后留言

          精选留言

          由作者筛选后的优质留言将会公开显示,欢迎踊跃留言。
          收起评论
          大纲
          固定大纲
          调整任务列表
          按照测试策略重组测试
          小结
          思考题
          显示
          设置
          留言
          收藏
          沉浸
          阅读
          分享
          手机端
          快捷键
          回顶部