單元測試本質:面向邏輯塊

    單元測試是最早階段的軟件測試,面對的目標最小,可以綜合使用黑盒測試方法和白盒測試方法,按理說,單元測試用例的設計應該是最簡單的,但實際上,單元測試用例的設計常讓人感覺無從下手,這是什麼原因?是代碼真的不具有“可測性”嗎?還是測試思路和方法不對?正確的測試思路和方法是什麼?單元測試工具應該具備什麼樣的功能,才能支持快速地構建測試用例?

    大道至簡,意思是掌握了事物的本質,事情就會變得很簡單。反之,如果事情很複雜很麻煩,往往表示沒有抓住本質。

   
單元測試的本質是什麼?首先要看單元測試的目標是什麼。單元測試檢測代碼功能邏輯,實現高質高效的編程。只要真正做過單元測試的工程師都知道,單元測試要做的,能做的,就是檢測代碼的功能邏輯,扯上其他東西是沒有任何意義的。既然單元測試是對代碼功能邏輯的檢測,那麼,測試用例要做的,就是針對代碼的功能邏輯,設定其輸入,並判斷其輸出是否符合預期,從而檢測功能邏輯的正確性。功能邏輯是由什麼實現的?邏輯塊。所以,單元測試的本質,是面向邏輯塊,單元測試用例的本質,是邏輯塊的輸入輸出,也就是設定邏輯塊的各種可能輸入,及對應的預期輸出。

   
一旦我們把目光轉向邏輯塊,所有的事情就會變得簡單。來看一個典型示例。“典型”的意思是,如果這個代碼不會測,實際項目也就不會測,因爲同樣的測試問題會大量存在;反過來,如果這個代碼可以測得很好很快,那麼實際項目的測試就基本上沒有問題,因爲已經掌握了正確的測試思路和測試方法。這個代碼的功能是,取得職位列表,將職位標題拼成短信併發送給用戶。參數是一個數據流,包含用戶的手機號和想要什麼類型的職位等信息,程序從數據庫裏讀取對應的職位列表和一個映射表,映射表用來檢查哪些職位已經發送給用戶,然後把職位的標題拼成短信並且發送給用戶。代碼使用C++編寫,但它所表達的測試問題和測試思想,則是通用的。

   
   

    請想一想,這個代碼的測試思路是什麼?也就是說,哪些變量要設置輸入,哪些變量要判斷輸出?如果按照傳統的方法,輸入是參數,輸出是返回值,那基本沒法測。但是如果面向邏輯塊(上面的代碼分爲兩張圖片,第二張圖片實現函數的功能邏輯,也就是我們要測的邏輯塊),立刻就有了思路:輸入是鏈表對象objList和映射表對象map裏的數據,輸出是拼接出來的字符串,在兩個地方需要判斷它的值。

   
具備了面向邏輯塊的測試思路,就可以將“可測性”這個詞扔進垃圾桶了,除非代碼真的糟糕得太過分,否則都不難測。至於代碼之間的耦合,那是再正常不過的事情,代碼反映了客觀事物,客觀事物本身就是互相關聯的,代碼能沒有耦合?如果一個函數有多個邏輯塊,同樣很簡單,各個邏輯塊分別測試就是了。

   
面向邏輯塊,測試用例的數據也很簡單,因爲邏輯計算涉及到的數據往往很少,且一般是基本類型。上面的示例,數據算是比較麻煩的,多數代碼的測試數據量都少於這個示例。雖然這個示例的數據看起來很嚇人,又有鏈表,又有映射表,但實際上,邏輯計算中涉及到的輸入就是一些標題(title),字符串而已,也就是說,我們要加入到鏈表和映射表中的對象指針,不管它的類型多複雜,每個對象只需要設定標題(title)就OK了。至於輸出,也不過是字符串。總之,對於這個示例,用例的輸入是一系列字符串,輸出也是一些字符串。

   
傳統單元測試思想,是面向函數,即用例由函數的輸入輸出構成,這在一些特例中沒有問題,例如三角形函數、排序函數之類最底層函數。這些函數,只有一個邏輯塊,且邏輯塊的輸入輸出,與函數的輸入輸出完全一致,當然可以使用函數的輸入輸出來構建測試用例。傳統的單元測試用例設計技術,都拿這些特例作爲基礎,當面對含有耦合關係的代碼時,反而作爲特例來處理,要使用編寫樁代碼、設置模擬對象之類的麻煩方法來解決(實際上很多時候解決不了問題,例如,前面示例中的輸出怎麼辦?)。實際項目中,代碼存在耦合關係是常態,完全沒有耦合的代碼反而很少。這種拿特例當常態,拿常態當特例的方法,在本質上是錯誤的,因此必然很麻煩,從根本上造成了單元測試難度大、成本高。總之,面向函數來設計單元測試用例,測試將很困難,至於主張單元測試要面向對象、面向模塊,那純粹是胡扯。

   
有了面向邏輯塊的測試思想,測試思路是很簡單了,但是,如何設置邏輯塊的輸入值和輸出值呢?邏輯塊的輸入,除了參數、成員變量之類的常規變量,還包括底層輸入,即調用底層函數獲得的輸入,如前面示例中的鏈表和映射表對象中的數據;很多時候,還包括局部輸入,即在被測試代碼執行過程中對某些變量的實時賦值,如局部靜態輸入、中斷輸入、界面輸入等。邏輯塊的輸出,除了返回值、成員變量之類的常規變量,還包括局部輸出,即被測試代碼執行過程中對某些變量的實時判斷,如前面示例中直接發送出去的短信需要在發送前實時判斷。這些問題,恰恰表明了單元測試的另一個簡單:選擇工具很簡單。如果工具不能直接地、方便地設定邏輯塊的輸入輸出,那基本上沒法用,或者成本很高(至少十倍以上),因此,選擇工具的最主要指標,就是能否直接地、方便地設定邏輯塊的輸入輸出。C/C++單元測試工具Visual Unit 4可以通過在表格中填寫數據,直接設定邏輯塊的輸入輸出,例如前面的示例,使用Visual Unit 4,只要點點鼠標,在表格中填寫一些字符串,就可以構建出鏈表和映射表中的數據,以及判斷所拼接的短信是否正確。


   
也許有人認爲,對於前面的示例,如果面向函數,通過設定參數來獲得鏈表和映射表的數據,也可以達到同樣的測試效果,甚至可以同時檢測代碼所調用的其他函數,例如用於解析用戶信息的GetUserInfo ()函數,用於從數據庫讀取職位列表的GetJobList()函數。這種想法是完全錯誤的,白白浪費時間和精力,爲什麼?
    1、這些函數可能還沒有實現,這在並行開發中很常見;
    2、這些函數或者它們所依賴的函數在測試時可能被隔離,這在大型項目中很常見;
    3、相關設備在測試時可能不存在,例如,單元測試一般不連接數據庫;
    4、相關設備無法返回測試需要的數據,例如,一個取環境溫度的底層函數,總是返回固定值;
    5、即使以上問題都不存在,通過設置參數來間接獲得邏輯塊的輸入也可能非常困難,例如前面的示例,必須熟悉通訊協議,瞭解GetUserInfo ()函數的工作過程,並在參數中填寫正確的數據流,且數據庫裏有合適的數據,纔可能獲得鏈表和映射表中的數據。

    面向邏輯塊,則完全不需要考慮這些問題,無論多大的項目,無論多少人並行開發,都可以在開始編寫代碼時,就做到邊開發邊測試。至於底層函數,誰家的孩子誰抱,應該由編寫者直接進行測試,這樣才能全面地檢測它的功能邏輯。


   
總之,單元測試應該面向邏輯塊,只有這樣,才能迅速產生測試思路,才能快速構建用例數據,才能檢測功能邏輯的方方面面,不留死角,而判斷一個單元測試工具是否可以高效地應用於實際項目,最主要的指標是能否直接地、方便地設置邏輯塊的輸入輸出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章