單元測試理論
以下CODE都已PHP CodeIgniter框架為例
劇情模式
某天早晨,某鷹的SKYPE....突然叮咚了一下。
大神:
小鷹,因該要教你單元測試了。
小鷹驚訝的回:
大神,什麼是單元測試
大神回:
單元測試就是對每一個功能,做合理的預期測試,有了單元測試,才有辦法做TDD(TESTING DRIVER DEVELOP,測試驅動開發)
小鷹疑惑的回:
那單元測試的目的是什麼阿?
大神:
快速、品質、可維護
大神:
小鷹,你會員驗證會怎麼做。
小鷹很堅定的回:
輸入帳號密碼,然後去資料庫對應資料,然後有資料則...,沒有資料則...
大神回:
果然是一般工程師思維,我跟你講真正的TDD做法,首先創一個函式,
假設叫做auth_account好了。
先測試傳回是不是false,因為你什麼都沒寫應該要是false。
然後傳入帳號後,先假設資料已撈出給驗證,測試這測試是不是正常。
接著做資料的帳密比對,測試驗證傳回的結果正不正常。
最終他應該只會傳回true跟false來決定帳號是否通過。
如果通過時你要取得必要資料,則要另開函式(或物件方法)去測試驗證資料是否取得成功。
當你測試完全無誤後,你的code不用再寫了,因為已經寫好了。
小鷹趁機coding就回:
大神,那我這樣寫是單元測試嗎?
function auth_account($account){
$query = $this->db->where('account',$account)->get('user');
if($query->num_rows() > 0){
return true;
}else{
return false;
}
}
大神回:
no!no!no!你這樣就只有函式,但你沒去測試,而測試不是你做而是電腦做,
也就是說你要另外寫個code,去測試你的函式是不是正確的,
而那個code的驗證功能,幾乎所有的framework都有提供。
大神接著回:
給你一個觀念,絕大多數的工程師不寫程式,正是因為他們覺得寫一個code去測試另一個code,
是很蠢的一件事,因為覺得很浪費時間。
我不否認這會讓你codeing的時間變成二倍,不過話說回來,也不過就是codeing的時間。
假設你寫個功能要2個小時,那你可能就要多花2個小時去先寫測試,看起來很花時間對吧!
但是如果真的就只是這樣我就不會跟你提了,重點是假如你最後要花3天的時間debug,
當你做單元測試和測試驅動的話,可能debug只要花你3分鐘。
現在,你知道省時間是省在那了吧!所以單元測試是為了 DEBUG 與 維護方便。
舉個例子來說,如果是二天的工作,可能會花你四天,但是二個月的工作,可能只會花你40天!
甚至只剩4週。
這是小鷹想到什麼就回:
可是一般專案時間已經很緊湊了,所以是不是因為專案時間的關係,
所以很多工程師都沒有做單元測試。
大神回:
正解,大家都會因為時間太趕,就會覺得單元測試是多餘的,沒價值的。
等到軟體交出去後,等到客戶抱怨不好用,再來回頭修bug時,
你會發現工程師就是無止境的加班解bug。
小鷹又回:
不過專案經理跟業務好像不會把單元測試歸納到時間內
大神回:
這也是正解,因為沒有人會想到是用測試來寫程式,
而不是寫完再來測試,最主要的是,
每個工程師的「垃圾」回收機制不同,大部份的工程師,
都不做資源回收的,我這樣說好了。
一個簡單的5+3,答案是8,但我們要去驗證,
於是你會看到工程師這麼做,echo add(5,3);,然後當答案是8時,
他就把這行mark掉甚至刪掉,他卻不知道被他刪掉的這行才是寶。
因為我們可以另外開測試程式這樣跑
function test_add(){
$this->unit->run(add(5,3) == 8,'is_true','測試5+3是不是等於8');
}
測試時你可以任意改值去看是不是符合你要的答案。
大神回:
好了我要去開會了!自己領悟吧!
小鷹心裡想趕coding都來不及了,哪有時間在單元測試[某神 神踢 小鷹飛了]
這個劇情告訴我們心裡OS不要亂說出來(某神 巴頭),
說錯了是告訴我們單元測試對程式的維護與DEBUG來說是很重要,
所以做了單元測試只是會消耗一點coding時間,可是呢!
會節省DEBUG的時間與維護的時間。
之前有一個奉行TDD的開發團隊說,這像倒吃甘蔗,未來功能的維護與改版會有很大好處。
雖然想像也是這樣
但在寫code之前先寫測試的code,多數人不知道怎麼開始。
剛剛翻找了一下,鐵人賽由hatelove撰寫的TDD介紹文章,相當值得參考
那...測試的Code要怎麼確定是正確的?撰寫測試的測試的Code?
基本上幾乎每一套framework都會有提供單元測試的功能。
重點是在於你願不願意信任他。
不願意的話,就把測試值通通丟進去看他回應給你的正不正確。
正確的話就是可以信任的測試環境。
然而單元測試中最容易被忽略掉的不是單元測試這件事。
而是工程師會不會把「垃圾code」回收到單元測試去當測試值。
我相信很多人都在做單元測試。
但我不知道有多少人會把他刪掉的code或是mark掉的code回收到測試去就是了。
感謝大神補充
我才不是什麼大神咧。
純出來騙吃騙喝的。
測試的code是否正確,得靠兩件事:
有問題的測試程式,意味著本身也會出錯,導致測試無法通過,所以並不需要靠寫測試程式來測試測試程式。不過測試程式撰寫的品質與測試是否有效等等,還是需要利用像code review這樣的活動才能確保。否則我只要寫一堆assert(true)所有的測試就通過了
原來如此,PHP常常為了DEBUG,程式老是經常加上又拿掉,雖然想要作DEBUG開關來一次全開關但是資訊太多的話又模糊重點。所以放在單元測試的話就可以省下很多麻煩的程序了。
剛好最近CodeData網站有一篇文章可以相互參考:
http://www.codedata.com.tw/java/unit-test-the-way-changes-my-programming/
另外,大推tinalee提到的91大的鐵人賽系列作,這系列是我當屆最喜歡的。
是不是? 是不是? (撥瀏海)
tinalee提到:
是不是? 是不是?
不小心按到tinalee的iT邦檔案網頁,發現您是元老級邦友,在2008年就加入iT邦了。
可是中間有將近六年沒有在iT邦回應、分享、回答....
能說看看你到那裏去了嗎?
simon581923提到:
能說看看你到那裏去了嗎?
麥問,哩A驚
我覺得測試最難的就是決定要測什麼啊?
不過TDD的確是個好的方式,前面多花點時間,提升code的品質,也方便後續的維護。
可測試性通常也會跟系統不同部分是否緊耦合有關係。例如如果把商業邏輯放在Controller,那就會很難測試。但是如果用例如DAO的方式把商業邏輯從Controller切出去,Controller只呼叫DAO的方法,那商業邏輯就可以對DAO來獨立測試。
記得Alan Kay曾經在一個maillist中提到,在物件之間傳遞的「訊息」才是OOP的重點,不是類別繼承或是原型繼承。單元測試所測試的,也就是這些訊息。如果沒有有效的訊息,就無法做有效的單元測試。例如,我們可以這樣設計Controller:不接收參數,因為假設了router會依照url規則來正確呼叫他。不返回結果,因為可以用view engine直接output。這樣的Controller可以運作,但是無法作單元測試。如果改成這樣:
<pre class="c" name="code">
class module_a extends Controller
{
public function action_a($Request, &$Response)
{
........
$value = $model->getValue();
$Response->view->assign('value', $value);
........
}
}
雖然action_a沒有返回值,但是透過傳入的$Response物件中的view,還是可以檢查action_a的運作是否正確,所以還是可以針對Controller作單元測試。
簡單說,好不好測試這件事,關係到framework的設計以及自己的設計。不過以web的分工來說,商業邏輯是最需要作單元測試的部份,所以能測試的話就盡量吧。