※ 本文轉寄自 ptt.cc, 文章原始頁面
看板Soft_Job
標題

Re: [討論] 工作上寫單元測試的比例

最新2024-05-03 19:56:00
留言24則留言,7人參與討論
推噓2 ( 2022 )
因為大家的討論都很基於心法 實作上相對很模糊 利用這個機會也跟大家請教實作上的方式 因為最近工作被指派要針對公司產品的程式做整理,其實運作都還好 只是大家功能是一層疊一層,一堆巢狀邏輯,跟依賴中的依賴 也沒有任何的測試跟註解,當然也沒有任何測試 舉個例子 有個功能是儲值,可以接受銀行、信用卡、或是一個臨時帳號 然後儲值前需要身分驗證,然後銀行的話要檢查是不是有綁定 總之每個方式都有一些共用,或是非共用的行為 目前的程式像這樣 func 儲值(方式){ switch 方式{ case 方式1: if 符合條件1 { if 符合條件2 { if 呼叫api-1 成功{ 更新介面1 } } } case 方式2: 要符合不同的巢狀條件,然後呼叫另一隻api,一樣根據結果更新不同的介面 case 方式3: 又是不同的條件跟api } } 像這樣的程式,不知道測試怎麼寫? 單看這個函式的cyclomatic complexity,大概是20幾吧 如果把測試寫成涵蓋所有的可能 像是=> 當選擇方式1儲值,且不符合條件1,就要如何如何 整個測試就寫不完了 而且條件本身又依賴於其他的api或是系統參數 就算寫完了測試,涵蓋率雖然高,但我覺得是沒有解決核心的問題 我就只是要確認我程式本身的邏輯沒問題,不需要涵蓋別人的東西 這邊我實在很好奇大家的作法是怎樣? 是跳過不做,還是真的就寫一個涵蓋所有可能的測試 我的選擇是先重構啦 先定義一個抽象的RechargeCommand介面 包含執行儲值操作所需的方法 其中execute()會統一回傳一個Result物件,代表儲值結果 接下來為每種儲值方式實作具體的命令 例如BankRechargeCommand、CreditCardRechargeCommand和AutoRechargeCommand等 然後一些通用的行為,像是登入驗證、密碼輸入等,用裝飾者模式 定義一個RechargeCommandDecorator抽象類別,繼承自RechargeCommand介面 最後為每種需要共用的行為創建一個具體的裝飾者,繼承自RechargeCommandDecorator 例如 LoginCheckDecorator和BioAuthDecorator 呼叫的時候,根據需要去組合不同的儲值方式,跟需要的共同行為 回傳值也都是相同的Result物件 呼叫會像這樣(我是ios swift) let bankRechargeCommand: RechargeCommand = BankRechargeCommand() let loginCheckDecorator = LoginCheckDecorator(bankRechargeCommand) let bioAuthDecorator = BioAuthDecorator(loginCheckDecorator) let result = bioAuthDecorator.execute() 呼叫api的部分用策略模式做依賴注入 在command實作中,接收一個RechargeAPIService,預設是真實呼叫 做單元測試的時候就單獨傳入mock service 測試也改成針對不同的儲值方式做測試 而不是去涵蓋所有的可能路徑 這樣就可以大幅減少測試案例的撰寫,也可以節省複製貼上的時間 我只要確定我的內部邏輯正確,理論上不管怎樣的路徑應該都不會出錯 也不會被綁定在外部依賴 整個流程大概就是把一個巢狀判斷函式 更改為 command+decorator 模式,根據需要呼叫與包裝對應的行為 然後外部的依賴透過注入,在測試時使用mock避免被干擾 也請大家看一下 不知道這樣的修改是不是可以 還是應該以涵蓋所有的可能狀況為優先 @@? ※ 引述《TonyQ (得理饒人)》之銘言: : 先說我不是故意要回兩篇, : 但剛看到 landlord (就 joey chen, 江湖名 91) 在 FB 的回應,我覺得也蠻好的, : 他說他最近在忙沒空過來,我問過他之後幫他轉過來。 : 以下基本上逐字照轉 : (source from https://tinyurl.com/rxyerfyw ) : 其實講到底根本原因反而是跟產品程式碼的設計能力有關, : 產品程式設計得越好,測試程式越容易寫,越好測。 : 真正需要在測試中做假模擬(隔離)的部分, : 屬於外部(擁有權不在我們手上的部分), : 例如外部系統的服務(走通訊協定出去,且不屬於我們可以維護跟上版的服務)、 : 三方(package/SDK)。而 DB, redis之類的 cache 甚至是不需要特別被隔離開的。 : 這是由於現代科技的便利,讓我們有機會把越來越接近端到端測試的一類, : 比例逐步拉高的可行性比過去容易得多。 : 另一個重點則是當設計越來越偏向高內聚,simple design, : 把 code smell 消除到最後回很自然地提煉出 domain model, : 有了 domain model, : 最複雜的 domain logic 處理一堆散落資料的邏輯都被內聚到model裡面, : 沒有 application 層的依賴,model 的單元測試也很好寫。 : 結論: : 1. 要有能力在 legacy 上重構出可測試性 : 2. 要有能力做出穩定的端到端測試 : 3. 要能精煉設計,將散落的資料內聚在一起 : 來代表 domain 的概念提供 domain 的行為, : 因為設計上本來就沒有外層的依賴,model的方法也都精簡短小,甚至鮮少回傳值, : 自然 API 易用性跟測試都可以比過去萬惡的三層式架構+內嵌無限層依賴注入 : 的手風琴架構來得簡單跟好測許多。 : 現在大部分的依賴(注入)都不是本質上需要的,而是被開發人員硬生生切得支離破碎的。 : 補充一下 TonyQ 內文最後一點: : 「如果都沒被報 bug,你也沒有修改它的需要時,幫它加測試幹嘛?」 : 這超級重要的,這種情況下加測試往往適得其反, : 只會建立偽陽性/陰性的測試結果,勞民傷財還造成干擾。 : ※ 引述《TonyQ (得理饒人)》之銘言: : : 底下這是比較「野性」」的作法,算是實務專案的經驗: : : 其實我覺得你到一個完全沒有測試的專案,要分兩個策略: : : 1. 補重要主線的 integration test 反正哪邊常被報修就補哪邊。 : : 如果一開始補不上去就先做下一點,理論上常被報修的地方會一直出現在下一點, : : 累積多了就可以變成1了。 : : 2. 假設自己是維護歷史功能,提昇自己維護部分的可測試性。 : : 舉實際案例好了,假設我今天再做一個算金流手續費的專案, : : 發現過去算手續費假設有11個地方寫了11次好了,所謂的高耦合不外乎如此。 : : 我會先寫個 util 把輸入跟輸出「去狀態化」,然後針對這個 util 寫, : : 然後這個 util 的單位以「去狀態化」成本可控,可在手邊開發時間允許的範圍進行。 : : 白話文:我橫豎都得手動測試,那就把手動測試的部分, : : 盡可能的透過 test code 來進行。 : : 如果不想接著維護的話也很單純,任務結束後把 test code comment 掉或移除就行。 : : 題外話,11個地方,我會選擇先取代一個地方, : : 然後等其他10個地方有需求變更時,一個個整併,補強測試條件。 : : 很多人會說,那為什麼不一次改11個,理由是你的開發時間跟成本允不允許。 : : 更重要的是你的QA資源夠不夠,因為寫正常的Test最累的是準備測試情境, : : 這真的是會花掉比寫test更多的時間。 : : 如果列不出完整場景,貿然修改既有的code只是在裸奔。 : : 有需求的部分是被迫裸奔,但沒需求的部分不用主動當暴露狂, : : 等待驗證過再慢慢統一。 : : 大原則就是:你橫豎都是要測試的,只是手測還是寫程式測,除了跟 gui 有關的東西, : : 多數的情況下寫程式測試都還是比較省時間的。 : : 更棒的地方是,在這種策略下,你往往可以用比同事少30% 的時間完成任務, : : 而且因為你的測試成本比較低,所以品質也會比較好,出問題的時候也更容易對焦。 : : 然後我通常是會跟同事說我寫了幾個 test case, : : 他們願意看就看,不願意看我就放著。不會勉強他們加入。 : : 如果你做不到可以用比不寫測試更短的時間來完成任務, : : 那你學的測試根本性的就有問題,不寫也罷。XD : : 3. 極端情況: 如果都沒被報bug,需求也都很小? : : 那你操心他幹嘛 XD -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 59.120.34.91 (臺灣) ※ 文章網址: https://www.ptt.cc/bbs/Soft_Job/M.1714634857.A.75E.html

24 則留言

landlord, 1F
剛好之前寫過類似的題目,有帶到重構的過程跟影片

landlord, 2F

landlord, 3F
當時也有朋友支援各語言版本庫,給大家參考一下

landlord, 4F
當然,寫測試的話還是得知道有哪些情境,才能用測試描述

landlord, 5F
但這個重構的過程,即使沒測試,有pair跟 code review

landlord, 6F
也不會有太大的成本跟風險問題
神...Orz

brucetu, 7F
你的最後一段假設就是在說如果單元測試都沒問題整合測試

brucetu, 8F
跟上線理論上都不會爆掉

brucetu, 9F
實際上呢?如果這個理論正確那就不用寫整合測試了

brucetu, 10F
所以要保證不爆掉當然需要所有的輸入變數的可能性跟路徑

brucetu, 11F
都測試過,符合預期,才有可能保證上線不爆掉
其實因為我的是app,所以其實我這段測試其實更偏向整合測試而不是單元測試 只是也很好奇 所謂的整合測試,在牽涉到外部的時候,需要一起測嗎? 因為我的測試程式本身就包含了登入檢查、生物辨識、呼叫api 只是這些部份我使用的是mock 這些物件的測試應該用其他的方式或包裝,不應該影響儲值這件事跟儲值的測試 加上我回傳的是一個通用的Result物件 這個Result要不是成功(包含成功儲值的相關資訊),要不是失敗(失敗的相關資訊) 流程中如果任何一個環節失敗了,對呼叫端來說都是收到失敗的Result 這時候就是處理失敗的介面與邏輯 原則上我還是會在測試中補上測失敗的測試案例 但應該就是幾個常見的,不會去涵蓋每一個 我的目標還是很簡單,我要確認的是程式這邊的邏輯有沒有問題 輸入變數已經被command模式限制了,以這個案例來說,好像不知道還有啥變數 且不管哪個環節錯,他就是拋Result<Error> 額外的測試,說真的,我覺得自己按一下比較快 理論上也不應該出現沒捕捉到的錯誤 真的有...大概也是很稀少的案例,這個就交給內部測試或是實際使用者吧 冏rz
※ 編輯: langrisser19 (59.120.34.91 臺灣), 05/02/2024 15:59:22

TonyQ, 12F
"如果任何一個環節失敗了,對呼叫端來說都是收到失敗的"

TonyQ, 13F
這個可能 app 比較沒感覺,但是鑑別不同的失敗對debug重要

TonyQ, 14F
題外話你這個情境算是很特定的情境了,我自己在這個時候會

TonyQ, 15F
思考的問題是,「假設我的程式出錯了,我能不能避開最大的

TonyQ, 16F
傷害。」 這也是一個可以考慮的事情。

brucetu, 17F
理論上牽涉到第三方服務的時候你要mock第三方服務

brucetu, 18F
實務上第三方有提供測試區的話我會直接開一個測試區db直

brucetu, 19F
接在上面測試

wulouise, 20F
寫一個新的加測試

OldTjikko, 21F
巢狀太多了,重構看看能不能guard clauses

yamagishi, 22F
太巢惹, ealry return 用一下該測的東西分一下你自然知

yamagishi, 23F
道 test 要怎麼寫

new122851, 24F
當你發現UT寫不下去時,你要的是重構

langrisser19 作者的近期文章

Re: [閒聊] 騎車該怎麼兼顧家庭呢
再好奇問一下 因為有很多人說可以早起去騎車,然後買早餐去上班 有人說四點起也有人說三點起 不知道大家是怎麼做到的 我自己在疫情那一年,為了躲人群 有試過每天五點起床去運動,但就只有跑步而已 我覺得難度很高 如果要四點起床 算睡七個小時,等於
[問題] 變速的選擇
就是最近存好錢要換一台新車 在變速的部分,想要搭功率一起整合 老闆有根據我的需求給了三種組合,價格都是一樣的 不知道大家會考慮選擇哪一種 第一個是ultegra di2的變速本體 + duraace9200功率大盤 第二個是用rotor的方
Re: [問題]長距離的訓練規劃
借這篇文分享一下 像我的路線很像1的反過來 會從風櫃嘴下滑,再從北海岸街紅樹林走河濱回家 因為都是一個人騎 所以也是有預想過萬一在北海岸這段破胎怎麼辦 但真的發生才發現實在很難 我大概是一早七點多破在路中間 不幸的是我是無內胎,雖然帶了小花
Re: [寶寶] 是否要帶小寶(1y11m)一同出國旅遊呢
我剛好也是在你這個年紀帶小朋友去德國玩的 去之前也是先去了日本,測試一下短途飛行的狀況 都OK,我們就趁小朋友機票還有優惠之前去了德國自助 大概去了快一個月,前半程主要是在北德靠大眾交通工具移動 跑了馬拉松,也帶小孩去參加啤酒節 後半程換自
Re: [情報] 關於小朋友從事耐力運動的討論
有人說我的舉例被V打臉 但這不就是一個邏輯問題 回到問題本身 到底大家討論的是什麼 是青少年“不可以”訓練 還是“不可以過度”訓練 對象是針對想要認真從事這項運動的小朋友 還是只是把騎車跑步打球當興趣的小朋友 V的貼文不就是一直在強調,對少
更多 langrisser19 作者的文章...