最近很有幸的读了 郑晔老师的《程序员测试课》,想把每一章都写成一个读书笔记,来分享自己对“程序员写测试“的理解
一个问题:
为什么程序员需要写测试?
提升效率
随着软件项目的日益复杂和对工期的严格要求,如果没有一个高质量的代码作为基础,我们可能会不停的“改BUG”,不断的加班,最终导致不仅需求做不完,项目进度还受到影响。
方便重构
随着功能的不断增加,原有的系统设计可能已无法满足当前的需求,这时“重构”成为了必要之举。但面对遗留的系统,我们是否敢于进行重构?改动一个地方是否会影响到其他地方?如何确保代码的质量?答案就是编写测试。通过每改动一个地方就运行一次测试的方式来确保之前的功能不受影响。
辅助设计
能够良好测试的代码必然有着良好的设计。如果您的代码无法顺利进行测试,那么这可能意味着您的设计存在问题。
四个步骤
需求是什么
在开始每个功能开发之前,深入理解需求至关重要。明确需求对应的场景以及每个场景下的行为表现,需要与产品经理进行反复的沟通与确认。有效的“目标管理”是实现“时间管理”的最佳方式,而对于编写代码来说,需求就是我们的“目标”。
举例
- 实现一个简单的todolist,需求如下
- 添加一个todo项,并显示到“未完成”列表中
- 完成一个todo项,从“未完成“列表中消失,但是依旧存在在 “所有项”列表 中
- 查看todo列表,勾选“未完成” 那么只显示“未完成”的列表,取消勾选”未完成“显示”所有项的列表“
设计先行
在拿到了需求之后,需要考虑如何把这些需求拆分解耦。有哪些领域对象,哪些功能需要放在领域服务,哪些功能需要放在Repository部分。 最好保持核心代码的小巧,因为其他模块也会使用核心业务模块,太多的额外逻辑会产生更多的耦合
举例
- 实现TODO List 中
- 领域对象
- TODO项
- 领域模型服务
- addTodoItem,添加 Todo 项;
- markTodoItemDone,完成一个 Todo 项;
- list,列出所有的 Todo 项。
- 仓库Repository应该包括持久化存储相关的接口
- 领域对象
任务分解
- 从哪里开始?
- 应用服务开始,为什么?因为它离需求是最近的
- 例如:在todolist中因为没有应用服务,所以我们从领域服务开始。
- 如何让函数可测试
- 让函数最好有返回值,这样才能方便测试
- 例如:创建一个todo 项,函数就返回一个todo项
- 列出测试场景
- 很多时候我们都只是考虑的是正常的测试场景,但是异常的情况也是需要我们考虑的
- 例如:创建todo 项 中
- 正常参数的情况
- 参数为空的情况
编写测试
-
把测试的场景具象化成一个测试用例
- 例如
- 使用mock框架模拟一些服务,使测试服务独立
- 使用optional 处理空值情况
- 使用AssertJ 库来处理异常情况
- 例如
-
把引用的第三方库封装起来
- 例如
- 通过做一层薄薄的封装,将第三方代码与我们的代码分离开,保证你的代码完全由测试覆盖;
- 在测试覆盖率中,忽略这层封装。
- 例如