五、从宏观角度考虑单元测试
进行重构以使得代码更为清晰
- 单元测试可以有效的支持重构,在单元测试保障下,可能的错误会因单元测试的存在而被及时识别。
- 重构的范围比较大,这里仅说明方法级别的重构。
- 方法级别的重构的原则是将复杂的,能够隔离的逻辑单独提取,给与明确的方法命名,保证方法内的逻辑简洁,以调高代码的易读性和易测试性。
- 在进行完方法级别的抽取后,可以考虑将同类型的方法抽取到类中
- 可以使用IDE提供的工具进行重构,如Eclipse的Refactor
- 最后,不必过分担心重构所带来的问题,多几个方法或者类对性能的影响微乎其微。与其担心,不如先将代码进行重构,以为后面性能问题暴露后的优化进行准备
- 清晰的设计是为优化做的最好的准备
一些高层的设计原则
- 单元测试并非凭空出现的,编写单元测试的难易程度与系统设计息息相关
- 要尽量将类的职责单一化,一个类的职责越多,越有可能影响其他已经存在的行为
- 类设计原则 SOLID
- SRP
- Single Responsibility Principle 单一职责原则
- 类应该仅会由于一种原因发生变更
- 保持类的体积小,职责单一
- OCP
- Open-Close Principle 开闭原则
- 类应该对扩展开发对修改关闭
- 尽量减少对类的修改需求
- LSP
- Liskov Substitution Principle 里氏代换原则
- 子类应该代换基类
- 从客户视角看,子类重写不应破坏父类方法的功能
- 也即在任何情况下,子类都能够代替父类,从方法角度讲,子类不能破坏父类的功能
- ISP
- Interface Segregation Principle 接口隔离原则
- 客户不应被强制依赖它们不需要的方法
- 将大的接口类拆分为小的接口类
- DIP
- Dependency Inversion Principle 依赖倒置原则
- 高层模块不应依赖底层模块
- 高层模块和底层模块都应当依赖抽象
- 抽象不应依赖细节
- 细节应该依赖抽象
- 方法设计原则
- 命令和行为分离(command-query separation),也即方法要尽量减少副作用,要么查询,要么修改,不要都做
- 反面教材就是iterator.next()方法,难用,易错,怪异
- 单元测试的维护成本很高,我们接受它的唯一原因就是它带来了更大的收益
- 如果单元测试很难写,检查设计先,优化代码先
- 当系统的设计/代码质量下降,单元测试的维护成本会上升
- 最后,不断提高的单元测试覆盖率能够提升重构的信心
重构测试
一些常见的单元测试坏味道
- 不必要的代码,如在单元测试中无需捕获异常,直接抛出即可,JUnit可以自动判断该测试用例为false
- 缺乏抽象,如重复代码没有抽取,断言没有合并等
- 无关信息,像编写生产代码一样编写测试代码,注重易读性,扩展性,降低维护成本,增加业务逻辑,持续从单元测试中受益
- 臃肿的构造函数
- 多个断言
- 测试中存在无关的细节
- 含义不明确
单元测试路线图
- 重构代码,提升代码的清晰度和一致性
- 重构代码,在设计中支持更大的灵活性
- 设计系统以支持Mock
- 重构测试降低维护成本并提高易读性