TDD Practise

总纲

差的测试会增加维护的负担,好的测试才能指导开发。品质差的测试可能让开发慢如蜗牛。
测试需要达到目的的同时尽量保证不要给重构及变更带来阻碍。

测试

测试的粒度:单元测试 -> 集成测试 -> 验收测试(用户场景测试)

单元测试使我们优化内部品质:模块能独立于系统运行,说明其边界是清晰的,高内聚,低耦合的,更可能在其他地方复用。验收测试保证外部品质。

TDD四步循环:失败 - 报告 - 通过 - 重构

测试的好处:

  • 确定要实现的目标
  • 通过测试可以发现设计缺陷
  • 有助于促进上下文无关性
  • 促进通过分解、萌芽、打包来发现值类型和对象

起步

  • 新项目:做最少的决定,实现可行走的骨架,尽早暴露不确定性,启动TDD循环 -> 从反馈中学习。
  • 现有代码:回归测试 -> 单元测试 -> 改动。

设计

  • 对象间的关系:依赖、通知、调整
  • 封装:确保通过API影响对象的行为;隐藏:实现方式不可见
  • 组合比部分之和更简单
  • 上下文无关性

CRC卡进行辅助设计。

好的设计:

  • 根据角色进行命名,而不是根据实现
  • 使用小的方法来去掉语法噪声,更好的表达意图,最后达到跟读文章一样的效果
  • 分离并发策略与功能
  • 事件源外部化,不使用内部的定时器

编写好的测试

好的单元测试:

  • 针对行为进行测试而不是方法
  • 更愿意读测试而不是代码
  • 单个测试规模小,容易理解
  • 依赖在对象构造时传入
  • 模拟接口而不是模拟具体的类
  • 少量的预期
  • 准确指定应该发生什么,没有多余的指定

坏味道:

  • get查询方法暴露内部状态,破坏了封装
  • 对象名字出现连词(与、或、但是),通常可以重构以抽取对象
  • 接口以I打头,或者出现xxImpl:意味着实现不能很好的命名,实现包含领域信息,接口定义角色,总是有可能将其分开
  • 模拟值类型
  • 模拟一个无法替换的对象:单例是依赖关系、隐式依赖也是依赖
  • 构造方法太大,测试很难编写:可能可以萌芽或者打包新的类型;关注点不集中,可能需要分解为新的类型;对通知关系和调整关系使用默认值,提供方法修改它们
  • 测试中预期太多:测试的重点难以发现
  • 测试中存在很多与重点无关的代码,如用try catch块捕捉异常
  • 测试中存在具体值如null,应该为其命名以明确其含义
  • 测试出错难以定位问题:测试描述不清楚

编写测试:

  • 编写辅助对象进行测试,如FakeAuctionServer和测试数据建造者
  • 使用自描述的值(覆盖toString),或者明显的预装值(Integer.MAX_VALUE)
  • 忽略不相关的对象
  • 明确事务边界、使用压力测试来测试线程问题
  • 使用捕获通知来探测变化以测试异步功能,使用waitUntil assertEventually来检测状态变化
  • 提取测试结构是注意不要让测试代码太抽象,无需像对待产品代码那样重构测试,测试代码的最高目标是让测试描述目标代码做了什么
  • 如果类需要创建内部支持对象,在测试中该对象应该是不可见的,而不是mock它

其他:

重构:

  • 为潜在的对象命名,因为某个东西有名字了,就可以控制它了

日志:

  • 支持性日志是一项功能,是应用程序用户接口的一部分,可以用测试驱动
  • 诊断性日志用于开发者发现问题,可以不用测试驱动,可以使用通知而不是记日志实现