作者TonyQ (得理饒人)
看板Soft_Job
標題Re: [討論] 工作上寫單元測試的比例
時間Thu May 2 15:45:45 2024
※ 引述《langrisser19 (lan)》之銘言:
: 總之每個方式都有一些共用,或是非共用的行為
: 目前的程式像這樣
: func 儲值(方式){
: switch 方式{
: case 方式1:
: if 符合條件1 {
: if 符合條件2 {
: if 呼叫api-1 成功{
: 更新介面1
: }
: }
: }
: case 方式2:
: 要符合不同的巢狀條件,然後呼叫另一隻api,一樣根據結果更新不同的介面
: case 方式3:
: 又是不同的條件跟api
: }
: }
: 像這樣的程式,不知道測試怎麼寫?
ㄅ是啊,你應該是先有需求才有測試,
通常是先假設已經在線上的已經經過線上考驗。
如果沒有這種需求,你根本就不應該整理。
我個人認為任何在沒有需求的前提下情況下整理程式碼,
是一個浪費自己時間又沒意義的行為。
有需求,你就會相對清楚你要處理的邊界在哪,
而不是在思考我要處理多寬的問題。
比方說,今天是要修方式3裡面的某算式好了好了。
那就是先用重構工具把 //又是不同的條件跟 api 的部分抽出函示,
然後把 member 成員的部分都轉成 input 。
(這是我的作法啦,這算是應急的第一步,
這個可以再透過重構的推跟拉來把他攤成 module,
但假設今天的情況是比較快的模式。)
xxx(){
......
switch(xxx){
......
case 方式3:
方式3_implement(xxx,yyy,zzz);
}
}
public static 方式3Response 方式3_implement(xxx,yyy,zzz){
//又是不同的條件跟api
}
Test case:
public class 方式3TestCase{
public void Test方式3(){
var mockxxx="xxx", mockyyy="yyy", mockzzz="zzz";
var res = xxx.方式3_implement(mockxxx,mockyyy,mockzzz);
方式3Response.aaa.should().be(xxx);
}
}
通常我會先把「已經存在的案例」先寫成測資,
因為老話一句,改前改後都要對的東西可以當對照組。
========
通常以上在有正常重構工具的情況應該是幾秒鐘的事情,
沒有的話就是自己搬變數比較煩,應該也是十幾分鐘要能搞定。
這裡可能會有一個例外,就是方式3 如果沒有明確可驗證的回傳值,
(也就是 void )
這時候我自己習慣的是,自己根據重要的地方補 true/false 的 bool,
只驗這個必要的 bool,這就要花點技巧處理的地方。
總之你要有「可驗證點」,這是可驗證性中很重要的一環。
然後考慮到可驗證點,你一開始就把「所有方式」,
寫成一個大 test case 講白了只是在找自己麻煩。
你完全沒有必要去做一個這麼難的 normalize,除非結構真的超級漂亮。
我會說你原文作的事情,其實是蠻 anti pattern 的,
真實世界中往往80%以上的情況是帶不進去的。XD
會做到一半發現當初分開是有理由的,然後又走回起點。
我的習慣就是方式1做到寫一個 方式2做到寫一個,
起碼當我需要測試的時候,我有一個子點。
然後不會強求展開,就是「有發生問題的補」、「懷疑會發生問題的補」,
你沒有無窮的時間窮舉,所以問題永遠是限縮跟抽樣。
必要的時候我會把我不關心的選項直接寫成 if(xxx) throw new exception();
表示此路不通,來減少子支。
========
再來是推進 方式3_implement 的開發。
這時候我會評估一下 方式3_implement 的複雜度,
如果很大而且很難肉眼比對的話,我會複製一個
方式3v2_implement ,內容跟方式3_implement 一樣。
然後稍微調整 Test case:
public class 方式3TestCase{
public void Test方式3(){
//因為是static 所以可以呼叫,
//這裡可能會需要 visible to internal 或暫時開成public
var res = xxx.方式3_implement(mockxxx,mockyyy,mockzzz);
res.aaa.should().be(xxx);
var res2 = xxx.方式3v2_implement(mockxxx,mockyyy,mockzzz);
res2.aaa.should().be(res.aaa);
//這是為了避免自己腦殘,當然這樣改的前提是 input/output ,
//新舊一致的情況,通常都會有重疊場景,如果沒有就不用這麼做。
}
}
接著就開始從 方式3v2_implement 下寫新的實作,
然後反覆跑 Test方式3 ,然後看狀況加新的 test method ,補新的測資跟場景。
這裡的前提是無副作用,有副作用的就都得透過 mock 先把副作用的影響處理掉,
大多數的時間會花在研究副作用跟子類。(其實就是讀code)
就這樣而已,沒什麼 magic,
你想改哪,就只縮哪。
另外原本的 v1 雖然看起來是重複代碼,但是過一兩個週期就可以回頭刪除他了,
而且因為他照理說會除了 test 以外沒被連結又是新增程式,
對實際的程式基本上影響為0。
然後你會問我,那你原本想幹的方式1,2,3,4,5能不能整併,
施主這要看你的需求。
通常我的習慣是上面的方式3Response,我會試著帶進去看看,
方式3方式2能不能共用回傳值。
如果可以,那才有談的空間,沒有的話,該分歧就分歧,不要亂合。
然後至於假設在方式1,2,3,4,5 還有 token驗證之類的上游問題,
那就是另外寫 test ,我這個 test 只涵蓋這個下游情境。
用「限縮環境」來減少你一次要看的範圍,
強化你對局部開發的速度跟掌握力,也是可測試性中很重要的一環。
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 1.163.94.23 (臺灣)
※ 文章網址: https://webptt.com/m.aspx?n=bbs/Soft_Job/M.1714635947.A.D59.html
※ 編輯: TonyQ (1.163.94.23 臺灣), 05/02/2024 15:48:33
※ 編輯: TonyQ (1.163.94.23 臺灣), 05/02/2024 15:50:41
※ 編輯: TonyQ (1.163.94.23 臺灣), 05/02/2024 15:51:46
※ 編輯: TonyQ (1.163.94.23 臺灣), 05/02/2024 15:53:39
※ 編輯: TonyQ (1.163.94.23 臺灣), 05/02/2024 15:54:45
※ 編輯: TonyQ (1.163.94.23 臺灣), 05/02/2024 15:56:40
※ 編輯: TonyQ (1.163.94.23 臺灣), 05/02/2024 15:57:34
※ 編輯: TonyQ (1.163.94.23 臺灣), 05/02/2024 16:18:42
1F:推 ian90911: 感謝分享 05/02 16:27
2F:推 now99: 最重要需求不要三心二意xdddd 05/02 16:56
3F:→ TonyQ: 需求三心二意就是跟著最後一次的需求走,你有寫TEST也比較 05/02 16:57
4F:→ TonyQ: 理解哪些是破壞性的需求,哪些是延伸性(不破壞既有條件)y 05/02 16:57
5F:→ TonyQ: 的需求 05/02 16:58
6F:→ TonyQ: 因為不管需求如何變化,你都得確認最後的那個版本你寫的是 05/02 16:58
7F:→ TonyQ: 對的 05/02 16:58
8F:→ TonyQ: 然後可測試性高的程式碼,你會有比較多端點可以抽換。 05/02 17:00
9F:推 viper9709: 推這篇實際 05/02 21:14
10F:推 gmoz: 請公司多聘SDET $$$$$ QAQ 05/03 11:25
11F:推 new122851: 我的認知是整個project除了main method這個進入點不用 05/03 19:54
12F:→ new122851: 寫到UT,其他的所有method都要被至少一支UT程式跑到過 05/03 19:54
13F:→ new122851: ,包含static的method 05/03 19:54
這就是追求 coverage ,但實際上沒這回事啦,
多數專案自己能做到30% coverage 就很了不起了。
提這個概念只是跟自己的時間過不去而已。
14F:推 yc86209: 推一個 05/03 20:34
15F:推 wulouise: 當你一開始才0%coverage開始的時候,最重要的先做 05/03 22:52
※ 編輯: TonyQ (114.34.27.1 臺灣), 05/04/2024 12:52:57