來源:dellfox 的部落格
摘要:本文是我在“第十屆中國系統與軟體過程改進年會廣東會場”所作演講的整理稿,主要分享單元測試的一些要點、單元測試實踐的主要問題,以及如何來解決這些問題。
一、 單元測試概述
1. 1 什麼是單元測試
單元測試,就是針對程式碼單元的獨立測試。為什麼需要單元測試呢?這是程式碼的基本特性決定了的。程式碼有一個基本特性,就是對資料分類處理。
程式碼通常會有很多的判定。一個判定,就是一次分類。巢狀的判定,會使分類次數的翻倍。
如果我們在寫程式碼的時候,有一個分類漏掉了,就會產生一個Bug;如果一個分類,雖然寫了程式碼,但是處理不正確,也會產生一個Bug。一個函式要沒有錯誤,必須做到兩點:1,對資料的分類必須完整;2,每一個分類的處理必須正確。做到了這兩點,就可以說,程式碼的功能邏輯是正確的。
那麼,如何檢測程式碼的功能邏輯是否正確呢?
除錯,是臨時的,且不完整的,例如,一個函式有十種輸入,除錯能覆蓋五六種就不錯了。而系統測試,並不針對某個具體的函式,不關注某個函式的功能邏輯是否正確。
要檢測某個函式的功能邏輯,就必須要依照分類列出資料,檢測程式碼是否對每一個分類都做了處理,而且每一個分類的處理是否正確。
——這就是單元測試。
1. 2 單元測試的基本方法
由上面的分析可以看出,單元測試的基本方法就是:依資料的分類列出輸入,執行被測試程式,然後,判斷輸出是否符合預期。
單元測試能達到什麼樣的效果呢?那就是:無論別人怎麼樣,我總是對的!
這裡的“別人”,是指關聯程式碼。“我”,是指當前正在編寫或測試的程式碼。單元測試要做到的是,無論關聯程式碼是否有錯,都要保證我是對的。具體來說,我要考慮關聯程式碼會產生什麼樣的資料,這些資料要如何分類處理,只要我的分類和處理是正確的,那麼,無論別人怎麼樣,我總是對的。
1. 3 單元測試的效益
單元測試的效益可以說是立竿見影,並且會推動整個開發過程的改進。
首先,單元測試可以保證程式碼的質量。因為只有單元測試,能夠全面檢測程式碼單元的功能邏輯,排除程式碼中大量的、細小的錯誤。
其次,排錯成本最小。如果在編碼階段同時進行單元測試,排錯成本可以忽略不計。但若到了後期,排錯成本可能會增長上百倍,要是產品已經到了使用者手裡,那造成的損失就更難說了。
第三,提升開發效率。單元測試可以讓程式行為一目瞭然,也就是程式行為視覺化。什麼叫程式行為呢?就是什麼輸入下,會執行哪些程式碼,會產生什麼輸出。如下圖,黑色的程式碼是當前輸入下所執行程式碼。
如果我們寫幾行程式碼,就可以看到程式的行為,相當於寫文章時上下文可見,這可以促進我們的開發思維。如果我們的思維有了偏差,也可以及時發現。如果程式碼中有了錯誤,也可以隨時排除。
那麼,是不是整個專案的所有程式碼都做了單元測試,才能得到這些效益呢?不是的。80:20規則,在軟體開發過程中也存在。也就是說,80%的程式碼錯誤,可能存在於20%的程式碼中;80%的編碼、除錯成本,可能會消耗在20%的程式碼上。這20%,就是演算法密集度高的程式碼,也就是功能邏輯複雜的程式碼。
這些程式碼可能只有20%,但是卻可能包含了80%的錯誤,消耗了80%的編碼、除錯時間,即使只對這部分程式碼進行單元測試,在提升產品的質量和開發效率方面,也會產生立竿見影的效果。
第四,自動迴歸。如果沒有單元測試,系統測試發現了錯誤,當然要修改程式碼,而修改程式碼可能引入新的錯誤,又要進行全面的系統測試,這樣就可能陷入迴圈,這通常也是專案延期的主要原因。
如果有了單元測試,修改程式碼時可以通過迴歸測試馬上檢測是否引入了新的錯誤。所謂迴歸,就是回覆到原來正確的狀態。
正是迴歸測試,使單元測試對整個開發過程的改進都產生積極影響,使專案適應頻繁變化的需求。單元測試是敏捷開發的基礎和核心,反過來說,有了單元測試,開發過程會自動趨於敏捷。單元測試也降低了後期測試的壓力。
二、 單元測試實踐的主要問題
單元測試有個特點:測試簡單獨立的程式碼很容易,但要在實際工作中做好單元測試卻很困難。
根據我們的經驗,企業在實施單元測試時,通常會面對四大問題——
不願做:程式設計師沒有單元測試習慣。
沒時間:編寫測試程式碼需要耗費大量的時間,專案的週期可能不允許。
做不了:程式碼具有較高的耦合性,使單元測試難以進行。
做不好:測試效果不能令人滿意。我們通常會以覆蓋率來衡量測試效果,但要實現高標準的測試覆蓋很困難。
三、 解決思路和方法
如何解決上述問題呢?接下來,談談一些思路和方法,使用的工具是Visual Unit。Visual Unit,簡稱VU,是視覺化的C/C++單元測試工具。
3. 1 如何解決“不願做”和“沒時間”
對於“不願做”,我們採用的對策是視覺化,這個視覺化,是指程式行為可視,後面我會用案例來演示;對於“沒時間”,採用的對策是自動化,通過自動生成測試程式碼、自動打樁等功能,讓測試的時間成本最小化。這兩者結合起來,就是ETDD開發模式。
那麼,ETDD是什麼呢?
首先來介紹一下TDD,TDD就是測試驅動開發,這個大家可能聽得比較多了。ETDD就是Easy TDD,即:易行版的TDD。ETDD具有以下一些特點:
視覺化,在開發過程中,程式行為可視。
自動化,除了測試資料需要人工設定外,其他基本上都自動完成。
現實化,不一定要測試所有程式碼,在開始階段,可以只測試功能邏輯複雜的20%程式碼。
下面,我用一個案例,講解一下ETDD的過程:
假如我要編寫一個函式,它的功能是刪除字串左邊的空格。
先寫好函式的框架,能通過編譯就行。在編寫程式碼前,程式設計師必須要做的一件事情,是想清楚程式碼的功能。如果我們想的時候,順手把它記錄下來,就可以讓程式碼的功能更清晰、更明確。
我們現在來記錄程式碼的功能。這裡的記錄,不是文字形式的寵統說明,而是資料形式的精確定義,也就是用輸入和輸出的方式來記錄。
首先,記錄最基本的功能,也就是最基本、最常見的輸入和輸出。輸入一個左邊有空格的字串,輸出是刪除左邊空格後的字串,返回值跟引數的輸出是一樣的。
然後,記錄詳細的功能。例如,左邊沒有空格的,全是空格的,還有空字串。
把每種輸入的正確輸出也記錄一下。完成了這個工作後,程式碼的功能就完全定義下來了。
現在,我們開始編寫程式碼。我的編碼思路是這樣的:分為兩步,第一步計算左邊的空格數量;第二步,將非空格的字元向左移動,覆蓋掉左邊的空格。
以下幾行程式碼,計算左邊的空格,現在編譯一下。CTRL+F7。如果編譯通過,測試就會自動執行。
我們可以看到,輸入是什麼,執行了哪些程式碼,產生了什麼輸出。這裡,黑色的是當前輸入下所執行的程式碼,未執行的話會顯示為紅色。這裡全是黑色,表示當前輸入下執行了全部程式碼。如果我們想看一下計算左邊空格的結果對不對,這是內部的資料,要指定位置後才會列印出來。按ESC鍵回到開發環境。
用這種語法可以輸出內部資料,適合於程式設計師開發過程中使用。複雜型別也可以用同樣的語法輸出。
另一種輸出內部資料的語法是,在左邊的程式碼視窗,在要輸出的位置點選一下,右鍵選單選擇“輸出內部資料”,這樣填一下就行了。這種方式不會修改產品程式碼,適合於測試員使用。
再次執行後,可以看到,左邊的空格的數量是4,這是對的,那我們可以繼續編寫。