优点
为什么很多技术或者知识要说优点? 因为有些道理看着很简单, 大家表面上都觉得对, 但是做的时候又不去做或者做不到其中有一个很重要原因是骨子里或者潜意识并没有真实觉得这是对的, 一旦想去做的时候同时会冒出更多不去做的理由
方法更健壮
更明确方法的职责
很多小伙伴在编写方法或者程序的时候, 先简单写一下大体的逻辑好一些的, 在写完后, 会根据不同 "情况" 验证一下, 如果有错再继续修改但是往往更多的情况下, 自己也不知道这个方法对外是一种什么形态, 需要满足多少种情况, 在异常的情况下提供的是什么表现所以最终需要使用者 (有可能是服务调用者, 测试者或者真正的用户) 来纠正问题, 然后再去修订
这样一来, 整个编写方法的周期其实更长, 资源的损耗更大
明确服务职责边界
最好是单一的职责(web 层或者流程聚合的接口除外)
现在是做一个判空的工具首先要分析的是这个判空的服务范围和职责一个集合判空一个字符串判空, 跟一个同时支持, 包装类, 字符串(包括 Char 等), 集合, 数组, 字典, 对象等的判空这就是两个完全不同的职责不同的职责最终的 case 是不同的
明确正常 case
一般是根据第一步的服务范围和职责来提供的, 这样是黑盒, 和使用者的视角是一样的 (推荐) 也有喜欢通过白盒列 case 的, 通过 if 等拐点来确定 case(不是很推荐)最终要保证对的肯定是对的, 而且要和预期结果一样
明确异常的 case
特别重要的是需求明确的异常, 比如说, 需要去支付, 但是你的钱是非法的还有抽象域的一些问题要考虑: 比如说: 冥等, 批量操作时的原子性, 依赖服务异常等最终要保证错的一定要错
明确 case 输入
明确每一个 case 输入应该是什么, 只关注和这个 case 相关的, 这样每个都是具义的如果一个 case 有太多的输入和 case 无关, 最好是考虑对依赖的结点进行 mock
明确 case 输出
明确每一个 case 输出是什么这样可以进行断点和结果预期然后执行时, 就能反向知道这个方法提供的服务是否正确如果不正确的话, 需要修改方法
大胆重构
只有有 case 了, 才能使用自动化的验证否则有可能只是改了一个很小的地方, 但是会引起其他 case 的错误, 改一个小地点就得手动的把所有 case 测试一下而且最害怕的是历史方法, 因为没有人能说清楚到底有多少种 case
重构时错误常见的场景:
一个判断条件或者设计的链路, 想的是对的, 但是写的时候出错, 导致正常业务都出错了
误删或者重构时遗漏代码, 导致部分业务错误
让编写的方法更独立
一旦耦合度太高, 在造输入数据的时候就会特别困难这样也反向的能促进我们在写代码的时候尽可能的不依赖, 至少不深度或者嵌套依赖
比如: 以前是写个 a 方法, 要知道 b 方法使用 c 对象的 d 属性这样造输入的时候就特别难受所以就会促进我们变成写个 a 方法, 最多使用和关心 b 方法其他是 b 方法的职责, 让 b 方法自己去测试
这样也能让每个方法更原子和内聚
隔离依赖
无感依赖细则
不用关注依赖的细则, 特别是不用跨层或者跨服务去关注细节从树状结构关注点变为平级关注点从关注细则到关注服务
并行开发
以前的方式是, 相互耦合依赖, 上游没做完, 下游没数据, 没办法或者很难并行开发但是使用隔离后, 就可以基于接口的服务职责来 mock 预期的行为, 所以互相就不会依赖, 可以并行去开发
结果可预见
比较头疼的是, 要根据不同的业务 case, 造各种场景, 有的场景还要开关或者编数据等特殊方式才可以但是使用隔离 mock 后, 想要有什么预期结果是非常稳定的, 也是很简单自然的
比如: 有 N 个集合中, 调用指定的服务后, 如果有部分失败, 部分成功这个 case 用 mock 是非常好造的
解决重复问题
当前, 在编写单元测试的时候也会有很多工作量, 所以可以通过单元测试框架来解决重复的问题
mock 简洁化和自动化通过注解和 ioc 基本很容易做到
设置参数很头疼, 还有很多魔鬼数字, 有的时候还得硬着头皮造一些无喱头的数据
写什么
单元测试不是越多越好, 而是越有效越好! 进一步解读就是哪些代码需要有单元测试覆盖:(引用 Kent Beck)
逻辑复杂的
容易出错的
不易理解的, 即使是自己过段时间也会遗忘的, 看不懂自己的代码, 单元测试代码有助于理解代码的功能和需求
公共代码比如自定义的所有 http 请求都会经过的拦截器; 工具类等
核心业务代码一个产品里最核心最有业务价值的代码应该要有较高的单元测试覆盖率
怎么写
根据 case 准备数据, mock
触发验证场景
期待的结果是什么
何时写
写单元测试的时机不外乎三种情况:
在具体实现代码之前, 这是测试驱动开发 (TDD) 所提倡的;
与具体实现代码同步进行先写少量功能代码, 紧接着写单元测试 (重复这两个过程, 直到完成功能代码开发) 其实这种方案跟第一种已经很接近, 基本上功能代码开发完, 单元测试也差不多完成了
编写完功能代码再写单元测试我的实践经验告诉我, 事后编写的单元测试粒度都比较粗对同样的功能代码, 采取前两种方案的结果可能是用 10 个小的单测来覆盖, 每个单测比较简单易懂, 可读性可维护性都比较好(重构时单测的改动不大); 而第三种方案写的单测, 往往是用 1 个大的单测来覆盖, 这个单测逻辑就比较复杂, 因为它要测的东西很多, 可读性可维护性就比较差
我个人推荐的是, 先大体明确方法的职责和边界, 然后把突出的 case 大体设计出来然后和具体实现代码同步一来可以补充 case, 只有对需求有一定的理解后才能知道什么是代码的正确性, 才能写出有效的单元测试来验证正确性, 而能写出一些功能代码则说明对需求有一定理解了二来可以使用重构的思维去解决思考两次而且还互相打架的问题
陷阱
多验证点
多验证点的 case, 一旦业务稍微改变一点点, 很容易造能 case 的通过不了, 也说明了方法的职责不是很原子有可能可以进一步拆分
过度依赖上下文
说明方法不够健壮, 职责不清楚如果一旦上下文变更, 就会导致 case 的失败介时就分不清楚是上下文数据的问题, 还是自己服务的问题
还需要做的事
工具类库
虽然, 单元测试框架做了很多重复的事, 但是还有很多重复的事, 其实都是可以封装成工具类的
比如: 一个方法有很多参数, 然后每个参数都都可以赋默认值, 那就得手写半天像这种抽象上一致的都可以封装成工具类
规范
在不同的单元测试之间, 其实有很多重复的思考和沟通
比如: 单元测试的方法名怎么命名更好些? 一个方法放一个 case 还是多个 case 什么样的异常需要验证 case
有了规范或者规约后重复的内容可以通过代码片段, 文件模板等方式半自动化的生成, 甚至可以通过代码生成器等小工具, 默认把一些手工的操作怎么自动生成而且规范后, 大家阅读和维护单元测试的成本就会降低
理想状态的单元测试, 应该是只验证正确的业务点, 和异常的业务点, 以及一些从系统和抽象问题领域角度的异常业务点其他的要么交给工具, 要么交给规范
参考资料
为什么要写单元测试, 何时写, 写多细
mockito 官方文档
来源: https://www.cnblogs.com/ansn001/p/8655504.html