作者HZYSoft (PCMan)
看板Soft_Job
标题Re: [讨论] Unit test 的撰写请益
时间Thu Nov 10 23:21:01 2022
※ 引述《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
: --
:
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 114.25.70.74 (台湾)
: ※ 文章网址: https://webptt.com/cn.aspx?n=bbs/Soft_Job/M.1667904696.A.B26.html
: 推 vi000246: 你可以先读重构相关的书 11/08 18:53
: → GooseLover: 如果模组化有做好,那你1.做得是正确的事情。正常来 11/08 19:12
: → GooseLover: 说就是Function跟Process分开测,最後再来个整合测试 11/08 19:12
: → GooseLover: 。然後不要抗拒为各别Function写Unittest 11/08 19:12
: 我本来预期就是为个别档案的个别function写unit test,不过不确定这样做是不是有符合u
: nit test的特性
: 推 s06yji3: 单元测试的话1 11/08 19:15
: → foreverk: 用1吧,如果要测试的function长文章那样,那本来就应该 11/08 19:22
: → foreverk: 花时间写BCD的test case 11/08 19:22
: 谢谢各位大大,那我可以放心去fake了
: 推 s06yji3: 不知道你用什麽语言,通常会有tool帮你拦截dep的function 11/08 19:28
: → s06yji3: 然後去呼叫对应的fake function 11/08 19:28
: 应是用google test,没错,他可以去呼叫fake function。这部分的实作应该没什麽问题
: 推 MoonCode: 对了 不写这个测试会怎样? 11/08 20:14
: ※ 编辑: shane87123 (114.25.70.74 台湾), 11/08/2022 20:15:55
: → ssccg: 合起来测也是种测法,只是那就不是unit test了 11/08 21:14
: → angusyu: 专案如果没有在切介面,直接硬fake会写到怀疑人生 11/08 21:24
: 推 yamakazi: gmock 就是1啊 11/08 22:41
: → yamakazi: gmock gtest框架都有了,你想得到的问题大公司都想到了 11/08 22:42
: → yamakazi: ,直接整套拿去用就好 11/08 22:42
: 推 drajan: 没切介面就赶快 refactor 没测试的软体能跑吗 11/08 23:01
: 推 sniper2824: 1 11/08 23:51
: 推 viper9709: 推二楼 11/08 23:57
: 推 Lipraxde: 没出问题的 legacy code 就别想着帮别人加 UT 了,顶多 11/09 00:00
: → Lipraxde: 做做整合测试,别把自己搞到怀疑人生 11/09 00:00
: 推 lovdkkkk: 不确定目前程式的情况, 假如目前程式很乱, 有可能需要先 11/09 00:17
: → lovdkkkk: 做 2 快速加个整合测试, 重构一下, 之後再做 1 11/09 00:17
: → leo08210917: 介面切好 弄懂IoC、DI做mock很快 UT也方便 11/09 01:07
: → leo08210917: 旧的可以用防腐层切开 弄整合测试就好 11/09 01:08
: 推 prag222: 虽我单元测试没啥经验,但说真的就是程式太鸟才依赖性 11/09 02:21
: → prag222: 太高,相信用物件导向或设计模式都可以切乾净的 11/09 02:21
: → Lipraxde: 只会更糟吧XDDD 11/09 07:22
: → bnd0327: 如果BCD没有被其他函式使用,直接用真的BCD测也无妨 11/09 08:53
: 推 wulouise: 2不算UT,但是你在refactor前可能会写出2那样的情况 11/09 12:10
: → wulouise: 最好先用test framework测整合测试再来refactor 11/09 12:12
: 推 andy831020: 绝对是1 最小单元去测试 11/09 15:23
: 推 lestibournes: 最近也在工作上尝试导入,觉得应该是1。但光mock就 11/09 18:28
: → lestibournes: 一堆东西,还要担心测试把程式码绑死(因为mock/fa 11/09 18:28
: → lestibournes: ke的部分已经明确宣告在测试内),还是先硬着头皮 11/09 18:28
: → lestibournes: 先写了QQ 11/09 18:28
: 推 CoNsTaR: 切 dependency 用 mock,有测试环境的问题用 fake 11/09 22:33
: → acgotaku: 请善用DI,然後再写的时候尽量将ABC低耦合 确保你分开测 11/10 01:36
: → acgotaku: ABC的时候不需要在mock,做假资料的时候尽量是真实状况 11/10 01:38
: 推 s8952889: 当然是1吧,如果你今天改B的程式码结果A的单元测试错了 11/10 12:51
: → s8952889: 很奇怪 11/10 12:51
: → s8952889: 不过其实在单元测试的档案写整合测试也是没问题的吧 11/10 12:52
--
Sent from PCMan on PCMan's PC
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 36.225.92.96 (台湾)
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/Soft_Job/M.1668093665.A.BD5.html
※ 编辑: HZYSoft (36.225.92.96 台湾), 11/10/2022 23:26:00
1F:推 Hsins: Sent from PCMan on PCMan's PC by the author of PCMan - 11/10 23:24
2F:→ Hsins: PCMan. 11/10 23:24
※ 编辑: HZYSoft (36.225.92.96 台湾), 11/10/2022 23:27:39
3F:推 Sean0428: Good 11/10 23:28
4F:推 wulouise: 不过实作细节要分到多细还是看个人考量.. 11/10 23:32
5F:→ wulouise: 不然整个chrome只测UI好像也符合这个分法(?) 11/10 23:33
6F:推 peyton87: 推legacy系统要先大范围integration test。之前摸索时先 11/10 23:34
7F:→ peyton87: 搞了些mock物件,弄老半天只测一小块。直接做必要的fake 11/10 23:36
8F:→ peyton87: 然後大块功能的integration test,至少能测。 11/10 23:36
9F:→ peyton87: 因为先有integration test,才成功做了些重构。 11/10 23:38
10F:推 drajan: 同意在缺乏好的模组化跟测试的前提下 整合测试是最优先的 11/11 00:25
11F:→ drajan: 确保最大范围的抽象行为是正确的再去改内部的程式 11/11 00:25
12F:推 CKNTUErnie: 推 11/11 00:27
※ 编辑: HZYSoft (36.225.92.96 台湾), 11/11/2022 00:34:13
13F:推 noahleft: 推 11/11 01:45
14F:推 yuinami: 推 11/11 02:03
15F:推 whatzup1124: 推 11/11 02:23
16F:推 bnd0327: 推推 11/11 08:21
17F:推 lchcoding: 拜读 11/11 08:40
18F:推 CRPKT: 这篇每一段的概念都值得推一次 11/11 08:40
19F:推 sck921: 推 11/11 10:57
20F:推 alan5: 还在那边想是谁观念这麽正确... 11/11 10:58
21F:推 ntps60803orz: 为什麽全部function呼叫一次就可以达到100% coverag 11/11 11:25
22F:→ ntps60803orz: e?不考虑条件式吗? 11/11 11:25
23F:推 rizman28: 推 11/11 12:03
24F:推 ManOfSteel: 推 11/11 12:04
25F:推 Walkers: 先推 11/11 12:19
26F:推 newhandfun: 我们也是先整合再单元,原因就是觉得之後可能会重构 11/11 12:40
27F:推 richer6605: 推 11/11 12:44
28F:推 lukelan: pcman 大神推推 11/11 13:54
29F:推 scottxxx666: 推 11/11 14:09
30F:推 shibin: 推 11/11 15:34
31F:推 sky80420: 先推再看 11/11 17:35
32F:推 d07880201: 推 11/11 19:43
33F:推 bewitchsky: 推 11/11 22:22
34F:推 jskblack: 推受用 11/11 22:34
35F:推 devilkool: 推 11/12 00:27
36F:推 yupog2003: 我们公司完全照着这篇文章的观念在写unit test 11/12 10:13
37F:推 a4782887: 这篇写的很好 感谢分享 11/12 16:45
38F:推 jasonwung: 推 11/13 01:18
39F:推 remember69: 受用 推 11/13 14:49
40F:推 uziel: 同意,要测就测抽象行为 11/15 07:35
41F:→ zapion: 推这篇 11/16 09:47
42F:推 streakray: 推 11/22 12:10
43F:推 judy2r3: 实用正确 推 11/28 04:46
44F:推 slowwalker: 推 12/01 05:06