單元測試的真正意義(10/7更新)
就是Unit Test,結束,嗯?(不對阿,就真的是字面上的意思哦?)
不過確實,有很多人以為這東西是寫所有程序都應遵循的絕對準則,或者甚至以為Unit Test就是PHPUnit,JUnit,最嚴重的是還以為要涵蓋測試所有function,就是要確認我的資料處理完全正確...
(點選標題閱讀全文)
面對現實吧
1.單元測試是拿來"方便加速"解讀改寫別人code用,或者是"方便加速"以後改版debug的用途,所以如果你發現並沒有加速也沒有更方便,麻煩停下來思考一下是否根本搞錯用法了
2.對Unit以外的東西做Unit Test,首先增加開發困難與時間,不過最大問題是做出來意義也不大[註]
3.把PHPUnit當成Unit Test,強行將所有function硬寫出勉強能跑的測試碼,連以後一定程度改版變動的確認測試都辦不到,因為根本就無法通用
4.Unit Test更不是Self Test,有流程的東西你是想assert誰?全部嗎?那程式稍微有動到還不是要全部重寫...這樣真的能減少工作量與除錯負擔嗎?我想是不可能的
...好吧我覺得有必要解釋一下,到底unit test在做什麼的
很多人都只看到好處,但連怎麼拿到這個好處以及為什麼能拿到這個好處都不清楚
假設有個最基本的getter setter api ,或是最基礎的簡單邏輯API(不能參照到其他API,參考資料註2),我針對這個範圍作全面性的unit test建構
首先假使這個測試方式是正確的,我就可以確定這裡的getter和setter資料有一定程度的正確性
(正確性絕對不是100%,因為你也才用幾種方法測試而已,這也是為什麼我們在這地方只針對unit做test,因為unit通常狀況是最少的,回傳也最單純,是用少數幾種assert方法能確認最大正確率的區域)
請思考一下以下兩種function的差別
function getmyassert(int a)
return string
function getmynonunitassert(int a,int b,int c,int d,string e)
f=call function suba(a,b)
g=call function subb(c,d)
h=f+g+e
return json(h)
前面狀況單純,assert個幾種就能大致掌握狀況
後面的呢?你會發現assert下來很空虛,因為"我不管怎麼assert,好像都不能全面性的掌握測試狀況啊?而且還他O的超難寫測試程式,這unit test到底是什麼鬼?"
這裡大概就能知道,unit test拿來測整合測試是如此無意義,因為本來就不是讓你這樣用的
coverage是有cover到的"unit",絕對不是"function",要不然ignore就不需要存在了
這個UNIT應該具備什麼條件?
雖然沒有統一,但大致都應符合下列特點:
1.一看就知道是特定模式的getter或setter
2.即使有引用其他部分函數,但約有99.9%不可能出問題(例如jQuery,早就用到爛的資料庫lib
3.不是getter/setter,但可以簡單預期(assert)結果 [例如:自訂運算函數]
4.沒有參照到其他邏輯(可以參照到global既定變數/方法,這是沒有問題的)
5.退個一萬步請給我個可以簡單assert的方法(註3)
對以下的FUNCTION寫單元測試沒有意義:(他根本不是UNIT):
1.根本就是大量call api各種流程的組合(當你assert東西,還得衡量好多function有變因)
2.無論任何前提,mock object建構過程過於複雜者
3.程式離物件導向模式太遠,即使是單一Class但裡面含有太多邏輯
4.任何測試用意當前提的function
5.非直觀預期的function[例如:拿輸入資料作處理後產生指定html頁面]
注意:如果看到其他文章,說寫好的測試要盡量涵蓋所有function到100%,這是對的
可是請看清楚他到底是在描述拿什麼在做什麼測試
機器和程式碼不是萬能的,需要測複雜邏輯測試的話
你去開發一百多行的判斷式來測三行複雜邏輯,根本不如手動執行幾次用肉眼觀察來的準確
那麼好處呢?到底這樣的test能帶來什麼效益?
很簡單,如果以後我懷疑getter或setter或什麼基本元件,是否有動到,是否不正確,是否修正了部分bug以後有產生新的問題,我跑一下對應的unit test馬上就能確認
有些人或許認為,怎麼測的東西這麼少,這麼簡單?是否太過膚淺?
其實不然,因為...專案只要大一點,setter和getter數量就不是十幾個了,是上百甚至上千
你想出問題的時候每次都把那龐大數量的setter/getter重新確認一遍是否有問題才開始DEBUG嗎...?
再來,為什麼Unit Test可以做自動化,同樣是因為即使只用少數幾種assert方式就可以大致確認狀況,請自己思考一下,複雜整合測試程式即使跑過大量assert你能放心嗎?還是會擔心永遠有狀況沒有預想到呢?就是因為Unit Test的assert的方式直觀簡單,所以能用自動化測試,切勿本末倒置妄想"可能有辦法自動化測試檢證我寫過的所有東西的運作正確性",三個字,不可能
如果unit test沒有報錯,通常錯的就真的是使用API Caller(Call這些unit的function)的方式或邏輯有問題,因為unit是按照你期望的方式運作的,出問題我先跑過unit test以後,就能專注於追蹤API Caller的除錯,無須從頭重新確認是否是getter或setter一開始就運作異常才導致我使用這些unit得到預期以外的結果
這個才是真正的unit test產生效益的部分...
是這個效益讓開發與debug除錯加速,絕非什麼能自動檢測所有程式有無錯誤好方便,那是幻想,OK?
還有,先寫單元測試才寫程式這種蠢事,抱歉我真的要說這是蠢事
會說這種話的人通常根本不知道自己在講什麼,常常連這程式是不是UNIT都不清楚
不然,我們來對賭一下
"告訴我你親自實際寫CODE先寫Unit Test才寫CODE的真實開發案例的確實流程"
不用貼給我看,你只要想想如果真的有人問你,你到底有沒有辦法拿出來,這樣就好
如果你拿不出來,或者只是個白癡到極點的EXAMPLE
憑什麼認為別人應該照做這種蠢事,甚至自己都覺得麻煩到死,要你動工先推拖個長時間
只會拿好處呼巄別人是沒有用的,因為這個好處你看不見,我也看不見
根本就是心裡安慰劑,如同上面說的
"一堆人都搞不清楚單元測試真正的用途是什麼"
單元測試很重要,但他絕對不是拿來全自動預防運作出錯用的,別傻了
還有
雖然全面性先正確撰寫單元測試且確認覆蓋可以減少以後API出錯GS的檢查時間
但是他會明顯大幅增加開發時間(至少是2倍,不懂原因的代表沒寫過或你拿EXAMPLE當蠢例子),各位觀眾有好好意會到這件殘酷的事實了嗎? [註4]
個人傾向self test的原因也是如此
宣導先寫unit test才寫code的常常會只看數據,只看coverage,"怎麼你有function沒寫到?"
不行啊,解釋根本聽不懂,只好硬著頭皮寫出來,"怎麼測試花這麼多時間"
沒有辦法你以為這東西可以一瞬間寫出來,這是不可能的,好嗎?
如果以上事情都有正確理解,還想推廣"完整單元測試",甚至要"所有程式撰寫前都必須先寫單元測試的"
那麼你或許是對的,你有正確的衡量到利益損失,明白了即使花費了兩倍以上的時間都要讓程式更穩定,那麼就放手去做吧,因為你認為值得
對了,這裡要澄清一點
撰寫真正的單元測試是會消耗相當多的時間,但絕不是浪費時間,對以後的防錯效率也是最高的
然而前提是對單元測試有正確的認知,最好的方法就是自己實際找一個有上線的搞一次
看你自己花多少時間,然後再來找一個準備開發的先寫看看,看花多少時間
前者會覺得根本寫不出來,這是正常的,但這邊有個誤解,你會有這種感覺其實並不是寫不出來
而是不可能瞬間完成
如果仔細點,完整版大概花個等同開發程式1.5倍的時間就可以了,完全沒讀過大概要2~3倍時間
除非你的東西都是超簡單的getter或setter(見最下面註3的調侃例子)那我沒話說
有邏輯的單元怎麼測才合理,是否有Mock Object,要怎麼使用,這都是要花時間思考的
如果堅持要完整版覆蓋UNIT到100%,請把單元測試當成等同原本的開發案的程度甚至以上的程度來看待,請勿當成附屬的東西看待,因為這樣根本就在作錯誤的期待
沒錯,東西是可以權衡的,因為我們明顯可以
"只對比較重要的東西寫單元測試"
測試很重要,非常重要
但是請先弄清楚你在用什麼方法對什麼函數做什麼測試
不適當的測試方式硬要套用,完全會是做白工,這種說法絕對不誇張
即使是適當的單元測試有帶來好處也是用大量的時間換來的,請務必不要故意忘記這件事情,天下沒有白減少的程式錯誤
---------------------------
註:如果真的能寫出來,這種測試方式對近期小幅度變更的版本還是可以正常運作的,然而也就這樣而已,只要稍微變化一大,這個test就不能用了,你還是得重寫
註2:可以參閱http://blog.miniasp.com/post/2010/09/16/ASPNET-MVC-Unit-Testing-Part-03-Using-Mock-moq.aspx,如果這些function輕易地就需要引用其他函數的結果,或者根本需要大量mock object才能完成,請反過來想想你在測試的是否根本不是unit
註3:有些人應該會有疑問,當你對一個DAO,你去斷言傳回來的是不是物件,這算不算unit test
答案是,"當然算,而且這才是正確做法"
請不要走火入魔去檢查裡面的值是否符合輸入需求,然後用一大堆判斷來確認和斷言是否有執行上的問題,請絕對絕對絕對不要幹這種蠢事,這絕非unit test
補充:那真的沒有去檢查物件裡面的值的狀況嗎?其實也是有的
當你資料參照的是其他來源的資料,裡面又有非常重要對運作有影響的東西時
那你勢必要確認這東西存在,而且需要嚴格正確,你非得assert他不可,因為他錯了就全錯了
註4:
function setExample(input){
this.Example = input
}
function getExample(){
return this.Example
}
看,我們單元測試就是!
function testsetExample(){
a = setExample(1)
assert a.getExample = 1
}
很簡單對吧,FCK很簡單對吧,你們所有程式所有函數都要額外加寫這種東西,就可以自動判斷了阿?
這麼方便的東西為什麼不做!
這種東西連我都不用出手你們就可以輕鬆完成了對吧!
,差不多就是這樣的感覺
你,被這樣的好處與優點打從心底佩服了嗎?
Counter
Labels
Archive
-
▼
2014
(58)
-
▼
09
(15)
- [轉貼]當你還能思考的時候,請不要自己放棄他
- 推薦閱讀的基本
- ubuntu的設定網路別直接改interface檔
- [轉貼]Mysql Prepared Statement
- [轉貼] SQL Describe改(查comment 權限的表)
- [轉貼]你以為一生光明磊落,事事對得起自己良心就沒事了嗎?
- 單元測試的真正意義(10/7更新)
- 舊版fuelphp vs redis 衝突筆記
- 重構程式的時候不要把自己當作業員
- 利用FuelPHP Migration拿建立資料庫的function
- FuelPHP Migration小研究
- ubuntu rename的真正用法
- FuelPHP的short tag
- ubuntu apache預設編碼
- apache多server基本設定
-
▼
09
(15)
沒有留言:
張貼留言