※ 引述《shane87123 (阳光大肥宅)》之铭言:
: 先说我对 Unit test 的看法:测试单元(可能是 function)的逻辑是否正确
: 好,进入正题
: 小弟最近刚工作,稍微读了一下负责的 project 的程式码后,
: 要开始开发 Unit test。
: 现况是,各个 file (.c) dependency 很重,
: 常常会有一份 code 内其实呼叫了很多别份 code 的 function,
: 举例来说
: A() {
: B();
: C();
: if (check)
: D();
: }
: 族繁不及备载,
: 而我目前设计有两个方向,
: 1.
: 将 B() C() D() 全部 fake ,单纯去测试 A() 的逻辑是否正确
: 这样做感觉上会比较单纯,一个 test case 只去 test A(),
: 而且不需要去 include B() C() D() 的 header,
: 这样一来 build 起来也比较容易,因为 include 那些 header 又会 dependency 到其他档
: 情况会非常复杂
: 缺点是 coverage 比较差,B() C() D()要额外去写 test case
先说结论,先都不要写。
Legacy system 要先补大范围的 integration test,确定整体的行为是对的。
如果 code 没有要再改,不用补细部 unit tests。
原因是因为,原本 API 可能因为设计不良,导致无法写 unit test
得先 refactor 才有办法让它变成 testable,这情况就要先 refactor 再补 UT
而 integration test 会一定程度减少 refactor 造成 regression bugs。
再来正名运动一下,fake/mock/stub 都是 test double,但用途不一样
https://martinfowler.com/articles/mocksArentStubs.html
fake 仍然是一个大致完整的实作,只是比较简化。
例如本来是 on-disk 的 key-value database,fake 就可以用 in-memory 的一个
dictionary 物件换掉,简化许多但是 API 有提供 "一样的行为 (key 的读写)"
stub 则是多半只会 return 固定的 constant,本身没实作行为
用 test double 主要好处是减少 dependency,然后测试速度会快。
测试大规模系统,如果读写档案,读写数据库都是真的去读,那就牵扯到跨系统整合
会花超多时间跑,也比较是 integration test
Unit test 的目标要能快速给予回馈,所以很强调执行速度。用 test double 换掉
同时能提升 isolation,也避免一些意外状况造成测试失败。
例如程式逻辑明明没错,却因为外部因素而失败跑不过,
所以 unit test 的书上多推广用 test double。
但实务上,持续维护 test double 和真实物件的行为一致,是一件高成本而且困难的事
如果你改变了真实物件的行为,所有有用 test double 的地方都要重新改写
而且 fake 物件实作有是可能会错,那反而会制造更多问题
所以 To fake or not to fake? That is the question.
后来有些书上就提倡 prefer real object,能用真的就尽量不要用假的。
根据专案的状况,你可以选择最适合的方式,没有绝对正确答案
然后目标不是高 coverage。目标是最 business critical 的 path 都有测试到。
coverage 要高很简单,全部 function 呼叫一次,然后不要检查任何东西
coverage 就会 100%,但什么也没测试到。
只要关键行为都测试到了,不用太纠结 coverage 数字高不高
: 2.
: 直接把他们 include 进来,build failed 就 include,直到 build 过为止
: 这样的好处是不用去实作 B() C() D() 的 fake,
: 但就会让整个 unit test 的 dependency 很重
这有时候是合理的作法。
Unit test 是依照"行为"拆分,不是依照 class/function 拆分。
不是一个 function 就对应一个 test,而是以"user 可见的行为" 来拆。
一个功能/行为常常需要多个 functions 组合,这些 private function 都是"实作细节"
是没有必要测试的,你只需要测试 public interface.
同理,一个功能由 A/B/C 三部分组成,A 呼叫 B 和 C,但实际上对外使用者只用 A
那 B/C 可以视为 A 的实作细节,这时候可以只针对 A 做 test
一个 class 的 public API 可以是另外一个 class 的 implementation detail
所以不是一个 class 就要一个 test,实作细节不用测,要测试大的行为。
: 个人偏向1.,毕竟 unit test 就是去测试 function 的逻辑性,
: 在其他 function 对测试 function 没有 side effect 的情况下(如不会改变某变量的值?
: 将他们 fake 掉而只是单纯的去 test 该 function 而已
: 但我第一次接触,不太知道何时应该去 fake (或 mock) 一个 function QQ
: 我只是有这两种想法,两个其实天差地远XDD
: