軟件開發實踐:我的單元測試歷程--這是一根不錯的拐棍


其實,最開始我是不做單元測試的。想想有那么多的代碼,那么多的邏輯,要寫多少單元測試函數。真正能實現百分百(更百分之九十)的代碼覆蓋率嗎?想想就不可能,那得寫多少代碼啊!有用嗎?而且如果代碼重構,以前的單元測試函數豈不廢了?

1最初對單元測試的思考

讓我第一次認真思考單元測試,是有一次,軟件通過了功能測試,自己又想重構代碼的時候。

如何保證代碼重構后,不引入新問題呢?這時候想到了單元測試。

1.1 緣由

要是某個模塊的接口(注意:本文中的接口是指外部可訪問的函數或變量-變量非常不推薦,而不是c#和java中的interface)是明確的,又有單元測試做保證,不就可以放心重構模塊內部的代碼了嗎?

1.2 接口與覆蓋率的思考

這時,我意識到,接口很重要,有好的接口,代碼就有了邊界,在接口不便的前提下,邊界內的代碼可以隨意動。

也認識到,單元測試可能沒有想象的那么多。

如果接口是內部接口(不作為庫使用),那么只要調用的地方覆蓋到了也就行了。

我還意識到,單元測試不能注重代碼覆蓋率,而應該注重邏輯覆蓋率或功能覆蓋率。

寫單元測試不能深入到代碼中,而是應該站在接口的角度,考慮有多少種調用情況。從這個角度看,單元測試應該是黑盒測試。有多少人被代碼覆蓋率弄得暈頭轉向,可嘆!

如果代碼習慣比較好,沒有無效代碼,覆蓋了完整了邏輯,代碼也就基本覆蓋了。所以整潔的代碼,走心的編碼是必不可少的。

后來我了解到,很多人認識單元測試,都是從支撐重構角度去思考的。所以說,重構是很重要的,至少它會引領你去思考單元測試。

這里有個誤區,如果不認真下手寫單元測試是認識不到。那就是很多人認為可以先編碼,編碼完成后,甚至到重構時再寫單元測試也可以。持這樣觀點的人,應該沒有真真正正寫過單元測試,或認真思考過單元測試。

其實,這里也有另一個引人思考的問題:我們應該什么時候重構代碼?顯然過了功能測試后再重構代碼,一定意義上來說,有點晚。

1.3 單元測試與開發時間

我還感覺到,單元測試可能會降低我們們的開發時間.

拿web測試為例(此處不是說頁面修改),我們在修改了代碼,需要編譯,發布,打開網頁去驗證我們的修改,有時還需要登錄,打到位置再測試,如果頁面有緩存,則要處理緩存。這多麻煩,容易出錯,用時還長,很多還是重復的步驟。如果有單元測試,按一個鍵就搞定了(這是我當時的想象)。

在認識到單測試有用,測試函數可能沒有想象中多的時候,我就開始有意去做單元測試。

2 對單元測試的探尋

2.1 第一次接觸單元測試

在剛開始參加工作時,曾用junit寫過一些單元測試.

那時對單元測試現解不深,而且做的是對日外包項目。該項目刻板到每個函數都要寫偽代碼,單元測試能寫成什么樣子可想而知了。所以這次基本沒得到多少有益的經驗。

2.2  尋找測試框架

由於我在用c++,不知道還有gtest,nggtest框架,也許那時還沒出來,或者名氣還不大,再或者我找東西的方式有問題,反正我沒找到一個合適的框架。我為此浪費了不少時間。其實,我們完全不需要執著於什么框架。這也顯示了當時我不知道單元測試怎么做。

及至后來看到一個開源項目live555(或者是其它項目,記不清了)。它的單元測試就非常簡單,直接printf出結果,也沒有自動對比正確性。我忽然意識到,什么形式框架根本不重要,重要的是我們想要的是什么。

於是,我開始不用框架,自己寫一些簡單的單元測試程序。由於單元測試函數共同點太多,例如初始化,判斷,統一執行等,很快我演化出了自己的測試框架,這里不再詳述。

2.3 單元測試的邊界

最開始寫單元測試時,寫的並不好。主要是分層分的不好。經常一個測試跨多個層,甚至連通訊軟件也用上了,例如ActiveMQ。

一度導致我苦惱很久:通訊層怎么測,數據庫交互怎么測?

后來才想明白:ActiveMQ和數據庫與測試有什么相關,都是第三方的東西,去測第三方東西干嘛,就當成它們都是正確的就行了。

我們主要目標測自已的邏輯是否正確,至於數據從哪來,到哪去,只要數據正確,可以默認都是好的。

由於當時不知道mock,在這一個塊花的工夫還真不少,例如自己重新寫一個被調用的邊界函數,里面提供數據,或對數據進行判斷。

3 單元測試的一些思考

3.1 單元測試有利於降低代碼的耦合度

一直以為寫自己寫代碼很謹慎,對待接口函數很重視,寫了單元測試后,才發覺做的也還是不夠。

記得有一次寫服務程序,周期運行。在函數中用到了系統時間,后來發現不好測。因為我們無法控制系統時間,所以每次要等一陣才能運行一次測試,很不方便。

這使我意識到這里對系統有了依賴,應該去除這個依賴,於是就將系統時間作為接口參數傳進去,測試就方便多了。可以傳入想測的時間,進行測試。后來了解到mock,才知道可以mock系統時間,但我總覺得去除系統耦合性是很有必要的。

3.2單元測試與重構

一直在思考,什么時候做重構最合適,功能測試完成后,顯然不是適合的時機。因為一改動,可能造成功能測試白測了。

我一度認為,在完成幾個類時,回頭看看有沒有共同點,如果有,則需重構。這時是個合適的時間點。又認為一個類或源文件完成后,適合重構,修改一下命名,添加一下注釋,拆分一下長函數。

寫單元測試后才發現,重構適合在一個對外接口函數完成后,做單元測試的時候。 這時候,可以通過單元測試驗證接口設計是否合理,邏輯是否正確,分層是否合適。

3.3什么時候開始寫單元測試

什么時候開始寫單元測試好呢?

TDD說寫代碼之前先寫單元測試,我沒有嘗試過。

在編碼時, 我不想讓單元測試打段我編碼時連貫的思路,所以我選在第一個對外接口函數完成時。

當然,也可以選在想編譯一下,看看是否能編譯通過的時候。

但是千萬別選在所有接口函數都寫完后,因為這時很可能你的代碼由於耦合度太大或分層不好,已不具備可測性;還可能由於感覺測試代碼量太大,放棄寫單元測試。

3.4 單元測試代碼量大嗎?

這里就說到測試代碼量,很多人不寫單元測試,就是覺得單元測試代碼量太大,性價比不高。

其實真正寫單元測試就會發現,單元測試量不大,一個接口函數最多也就五六個測試用例,再多就需要思考函數設計、定位是否合理了。

我的實踐情況是:單元測試用例就四五個,睛着函數看半天,需不需要更多的測試函數呢?結果發現,沒有,就這些。我經常有這種體驗。

這些測試函數一般不會太長,同一接口函數的測試函數應該比較類似。

3.5單元測試占用時間多嗎?

很多人覺很寫單元測試很耗時,會浪費大量時間,其實不然。

第一,  單元測試的同時,把重構給做了,節省了部分重構的時間。

第二,  單元測試早編譯,早發現問題,處理問題也容易。

第三,  單元測試利於調試,我們寫代碼,大半時間在調試,調試時搭建環境需要不少時間,再執行一些重復的步驟進行調試,有時候需要重復多遍。

而單元測試,只需要運行一下就可以了。

第四,  還有,寫一個程序,自然希望快速調用一下,看看效果,這是一個比較自然的過程,單元測試正好可以滿足我們這個需求。

3.6單元測試難寫嗎?

單元測試並不難寫,我寫單元測試的思路是,寫最少的代碼,完成我想要測試的功能即可。感覺寫單元測試就像寫一篇有思路時的作文,還是很順暢的。

3.7 mock

mock是我最近才了解到的技術,這個技術非常有用。

以前寫單元測試,遇到調用外部接口時,會自己實現一個版本,並在單元測試中以自己實現的版本替換真正的外部實現,有了mock后,我們就不需要這樣做了,只需要簡單調用mock就行了,而且mock可以做得更好,更多。

例如,有個定時器或線程,要是沒有mock,就不好測,除非將線程邏輯函數寫為公用的,有了mock就方便多了。只是mock的語法稍微復雜一些,需要學習。另,用mock的測試用例可讀性會差一些。

3.8 單元測試在編碼時作用明顯

我現在的習慣是,如果新功能,不管項目要求不要求寫單元測試,我自己都會寫單元測試,即使不會提交到代碼庫中。

不寫單元測試,就像登山沒有拐棍,不是不行,但是總覺得不方便。

編碼完成后,我就對單元測試不怎么關心了,這習慣不好。這也和自己最初認為的“單元測試是為重構保駕護”不一致。

我覺得單元測試真的在編碼階段起的作用比重構時要大,或者說在編碼時,我己進行了大部分的重構。

4 單元測試就是一根登山杖

最后我想說的是,單元測試很有用,但不是必須的。這只不過是一種比較好的實踐罷了。

不用為此糾結。如果你想用,建議立即下手使用,多想多做,自然能體會到它的好處。

沒想通,沒興趣,不做單元測試也沒什么,畢竟很多時候我們不寫單元測試也這么過來了。

只是於我個人每當遇到難解的bug,我會感嘆,當初要是有單元測試多好。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM