※ 引述《shane87123 (阳光大肥宅)》之铭言:
先说在前面
虽然听起来很干话,但很多东西没有标准答案
有时是合适度的问题,也可能是喜好(品味?)的问题
同一个题目,实际的 code 长得不一样,可能也会用不同的方法处理
另外,除了资源丰富到人力充沛到不行的专案,以及几乎没有时程压力
的专案(很多开源专案属于后者),少有专案能把测试能做到真正完整
很多时候是取舍,花时间写多少测试,能 cover 到最大的范围
剩下的部分靠整合测试(包含自动与手动)来抓
经验能让人能更快做出效益更好的取舍
: 先说我对 Unit test 的看法:测试单元(可能是 function)的逻辑是否正确
: 常常会有一份 code 内其实呼叫了很多别份 code 的 function,
: 举例来说
: A() {
: B();
: C();
: if (check)
: D();
: }
在“unit test”的前提之下,A() 这个“单元”要怎么被定义?
- B() C() D() 的集合体?
- 负责分派逻辑前往 B() C() D() 的 router?
- A() 本身就做了一大堆事情,BCD 只是辅助?
- 其他?
在不同的状况下,该测的单元功能会完全不一样
适合的可能做法也完全不同
例如说对于状况1
- 也许该把那个 if 检查丢进 D(),让 A() 就只是依序呼叫三个其他人的传声筒
接着不测 A() 但认真测 B() C() D()
- 没有副作用可以,有副作用的话怎么验证副作用?
例如说对于状况2
- 是不是该把 B / C / D 弄成变量传进 A?
- 没在写 C 不知道 C 能怎么做,function pointer?
- 或是有某种类似 router 定义的东西,然后测 router 功能?
例如说对于状况3
- 全部 fake 是不是比较快?
- 是不是该把这个 function 拆掉?
- 诶,看到七层的 if else,真的觉得拆的掉吗?
- 诶,看到七层的 if else,真的觉得要伴他一生一世吗?
其实“重构”是个很常该被考虑的选项。
但当然,重构有时是个繁重的工作,也不见得是最常被选择的选项
这也是 TDD 曾经风行一时的一部分原因,因为脑袋正常的工程师走 TDD
会避免自己写出“啊,这要能测会把测试写的有够屎”的 code
但走 TDD 也可能写出“看得懂想测什么,但逻辑怎么会这样切”的东西
另外自己经验是通常测试难写是跟资料有关
“某些时候”如果非常辛苦的把资料 IO 全部 mock 一层,后面会好做一点
例如之前看过 api 专案平常线上是戳 MySQL 拿资料
在跑测试的时候直接开一个 in memory 的 sqlite 来当后端
测试逻辑会先把资料塞好好再来跑测试
于是就可以测“拿文章的时候会把内文特定 tag 换成其他 table 的资料”
之类资料连结度高的东西
走这条路的话不 fake 直接把逻辑跑完也是个好选项了
或者不用全部 fake,可以只 fake 几个
这大概不算是“单元”测试吧
但该测的有测到,而且在艰苦的工程之后可以轻松往上叠一堆其他测试
(但..如果没有那么多东西要测,这会不会太厚工?)
另外不同程式语言(或不同辅助函式库?)的状况会天差地北
例如上面的 A(),如果是 python 的话可能可以不改 code
直接用 patch() 把 B() C() D() 变成 mocker,会回传需要的值
测试就直接检查 B/C 是不是每次都被呼叫到
D() 是不是条件对的时候有呼叫到
因为 mock 容易写,全部 fake 掉会变成更有吸引力的选项
所以回到原本的题目:
全部 fake 跟全部不要 fake,选哪边?
我的回答是:
这两个都是选项,但不是只有这两个选项。
例如重构,例如在其他地方准备抽象资料层,也都是选择。
甚至手上的专案也可能变出不同的把戏。
这些都是能打的牌,要看场上环境怎样,再来取舍该打哪几张。