在新浪微博上有個朋友問到這個問題:“有多少是測試年限在7年以上但還仍然在做大量手工測試的。。。。,請朱老師給指導一條明路”,限于微博140的限制,我還是在blog中來分享一些我的觀點:
手工測試工作并不比自動化測試設計,工具使用或開發(fā)簡單很多。你可以每年反思總結(jié)自己在測試分析的方法及能力上——減少測試對象遺漏,測試設計的方法及能力上——測試深度和去冗余,取得了什么進步,這就是你每年的努力提升方向。 自動化和工具畢業(yè)生都可以快速掌握,但如何設計一套漏測少,冗余少的測試用例框架則需要你豐富的手工測試經(jīng)驗為基礎;如何設計一套能縮短研發(fā)進度保障測試質(zhì)量的測試策略需要豐富的手工測試經(jīng)驗為基礎。我03年就開始開發(fā)自動化測試框架(可以讓功能測試人 員只需要會腳本的if for就可以完成自己的自動化測試腳本),一行一行寫的,相比國內(nèi)目前大多數(shù)搞自動化測試的人算走得早的吧,07年時還參與給某大型IT廠商(測試有 400人當時)進行自動化測試咨詢(自動化測試框架以外的自動化測試相關(guān)經(jīng)驗)。08年底出版的《軟件測試精要》一書中所提到的一些自動化測試的部分經(jīng)驗在2010年的時候還被華為內(nèi)部某些專職自動化測試工作人員提煉總結(jié)和傳播,內(nèi)部下載量上百。我從03年唯自動化測試工作論,到后面慢慢淡化,逐漸從事了其他測 試類型的工作后我發(fā)現(xiàn)在測試領(lǐng)域自動化測試的重要性沒我想得那么高,也沒那么關(guān)鍵后,從08年起我完全離開了自動化測試領(lǐng)域,并發(fā)現(xiàn)了更多有價值更有意義 更有挑戰(zhàn)性的測試技術(shù)創(chuàng)新專題。如果關(guān)注我的blog,我的新浪微博會發(fā)現(xiàn)這幾年我分享的測試技術(shù)和經(jīng)驗都是非自動化的,現(xiàn)在我還在路上。要知道測試的根 不是自動化,而是質(zhì)量,在質(zhì)量這一塊手工測試經(jīng)驗人員是大有可為,也只有依賴你來大有可為,自動化測試的同事搞不定質(zhì)量,也無法全局角度整體來提升效率, 自動化測試提升的效率從整個研發(fā)周期和測試周期來看,只能算是局部優(yōu)化。搞好測試策略,搞好用例冗余也都能整體提升測試效率。
一個好的測試用例是每個人都能執(zhí)行的測試用例,不管你是否是測試人員,不管你是否了解整個軟件的工作流程,你都能順利的執(zhí)行完測試用例,并對這個測試用例覆蓋到的功能點有了大概的了解。
好的測試用例的設計相當了軟件開發(fā)中 的詳細概要設計,要寫出好的測試用例首先要對所測試的軟件很熟悉,熟悉軟件的每個功能點和系統(tǒng)的整個業(yè)務流程。其次,對整個測試用例有個好的規(guī)劃,理清主 線,功能點的在哪個地方被覆蓋都是需要考慮的。最后,需要良好的心態(tài),寫測試用例是個繁瑣的過程,測試用例不是隨隨便便就能寫出來的,好的測試用例更需要 你在寫的過程中不斷去理清思路,并把每個功能點都恰當?shù)膶戇M去。
測試用例的規(guī)劃:
用例的規(guī)劃非常的重要,它決定整個測試用例的思路、風格、覆蓋率。即對整個測試用例的成敗都有直接的響。對測試用例的規(guī)劃我個人總結(jié)出兩條思路:一條是 用例的線性規(guī)劃,另一條是功能點覆蓋型。這兩條思路的側(cè)重點各不相同,各有優(yōu)缺點。線性的測試用例的要點是在理清每一條思路,即以業(yè)務線和流程線為主,每 一條線都是一個流程,然后把功能點穿插到每條線里去。把每條業(yè)務流程比作豎線,功能線比作橫線,那么功能點就是橫線和豎線的節(jié)點,這樣整個用例就是一張大 網(wǎng),我們可以隨時向網(wǎng)中添加橫線或豎線,使得覆蓋率不斷增加,“漏網(wǎng)之魚”越來越小。
另一種思路是功能點覆蓋型。在設計之前把要整套軟 件的功能點理清楚,這當然是非常的難的。但我們可以參考系統(tǒng)設計的功能流程圖,軟件的需求來進行分析和提取。還有一點就是測試人員的經(jīng)驗來完善所需要的功 能點。這種思路的重點是把每個功能點都要在設計中體現(xiàn)出來,以功能點覆蓋為主,不管工作的業(yè)務流程。也就是說是按照各個功能模塊進行劃分的,分模塊進行用 例的設計。
兩種思路相輔相承,各有各的優(yōu)點。在實際的執(zhí)行過程中,有時以業(yè)務流程來編寫比較容易,有時以功能模塊編寫比較容易。一個是以線為主,一個是以塊為主。
測試用例的設計:
規(guī)劃好測試用例的整體思路之后,就是測試用例的具體設計設計了。用例的設計的格式主要由測試環(huán)境,準備數(shù)據(jù),前置條件,用例ID,預期輸入值,期望輸出 結(jié)果,測試執(zhí)行結(jié)果和優(yōu)先級等幾個部分組成。其余的還有一些統(tǒng)計頁,打印輸出的模板等。個人認為用excel設計比較簡便,excel可以有多個頁面,一 個頁面為統(tǒng)計測試結(jié)果和用例維護,一個為測試用例的主頁面,還有一個頁面可以放一些打印后的模板。這樣的設計有利于用例的維護。
用例的預期輸入值和操作步驟是用例設計最重要的部分。設計好這兩個部分對經(jīng)后測試用例的執(zhí)行至關(guān)重要,特別是操作步驟的描述,要描述清楚每一步的操作步驟,這樣才能讓測試的執(zhí)行者準確操作,不會產(chǎn)生歧義。用例所寫的每一句話都應該清晰的,沒有歧義的,否則就會出現(xiàn)用例維護時,其他測試人員看不懂你寫的是什么,測試用例執(zhí)行的時候,看著很費力,達不到文中剛開始的要求。
測試用例的維護:
每個測試用例都要在經(jīng)后執(zhí)行的過程中去維護修改,使得測試用例的覆蓋率不斷提高。特別的測試用例的第一個版本時,需要維護的量是非常大的。我們可以邊測 試邊修改測試用例,也可以根據(jù)需求添加測試用例。每維護一次測試用例,就必修記錄下你修改的內(nèi)容,以便于經(jīng)后的閱讀和別人的維護。
以上是我近期對于測試用例設計的理解,也是我近期工作的一個總結(jié)和體會,測試用例設計是一門高深的技術(shù),也是軟件測試的重要組成部分,我們需要經(jīng)驗來不斷提升用例的質(zhì)量,設計出好的測試用例。
淺談淺談,各位都是
測試用例設計方面的高手,本篇只是拋磚引玉,分享下我在測試過程中對測試
用例設計和執(zhí)行的一些
感悟,也希望大家能有更好的觀點分享。
首先是測試用例數(shù)據(jù)的來源,測試用例中的數(shù)據(jù)來源于需求,規(guī)范的需求人員會將用戶的準確信息匯總傳達給項目組的開發(fā)和測試人員(當然需求不準確是另一回 事),測試人員需要驗證的是開發(fā)的實現(xiàn)是否符合需求的預定目標。在項目開始的時候,開發(fā)人員著手設計框架和編碼,我們測試人員則排計劃和準備測試用例。剛 開始的時候覺得沒有需求文檔一切行動就像失去目標一樣。有時需求文檔確實跟不上進度,測試人員是不是就不用工作等 著呢?完全不是。其實測試人員獲取該產(chǎn)品的詳細信息有很多渠道,文檔只是一種其中一種文字化、實例化的方式,比較規(guī)范。測試人員完全可以和開發(fā)、團隊 Leader溝通、交流,將自己對這個系統(tǒng)的含糊不清的地方一一明確,這樣既可以彌補需求文檔不足帶來的不便,也加深測試人員對該系統(tǒng)的理解。對于需求文 檔有遺漏的地方,測試人員可以通過需求評審(由客戶、需求、開發(fā)、測試一起討論)進一步明確需求。
其次是編寫用例了。個人編寫用例的習 慣是用例能夠覆蓋這個系統(tǒng)的功能點即可。用例設計的元素一般由ID、名稱、所屬功能模塊、測試前置條件、輸入數(shù)據(jù)、執(zhí)行步驟、期望結(jié)果和備注組成,當然為 了規(guī)范執(zhí)行,一般情況下還會加上執(zhí)行人、執(zhí)行時間和執(zhí)行結(jié)果。譬如設計一個創(chuàng)建對象或者子節(jié)點的用例,個人在測試步驟中只會寫“正常登陸系統(tǒng)后,點擊創(chuàng)建 對象功能,輸入對象名稱,提交保存。”期望結(jié)果就是對象能夠準確無誤的創(chuàng)建。這樣一看,好像少了點東西,對,邊界值檢查(包括空值檢查、最大值和超過最大 值檢查以及特殊字符檢查甚至同名檢查)。個人覺得邊界值檢查的方法確實好,能夠發(fā)現(xiàn)很多缺陷(前人總結(jié)的真理)。不過經(jīng)過權(quán)衡后,我還是沒有將這些譬如 “輸入超過20個字符檢查”、“不輸入任何值檢查”、“輸入相同名稱檢查”等字樣寫在用例里。為什么?
聲明一下,我并不是反對用例寫的 很詳細,我只是覺得上述內(nèi)容填寫(又叫過度設計)對測試用例來說沒有必要,有點本末倒置。一來簡介版的用例可以很小,很方便維護,同時后續(xù)需求變更后很容 易修改甚至不用修改;二來給用例執(zhí)行者最大的自由(我不反對將編寫該用例時自己想到的閃光點寫在里面),對于熟練的測試工程師來說,看到輸入框的第一反應 就是不輸會怎么樣,超過了會怎么樣,不滿足你的要求你會怎么樣,可能是自然反應吧(我就是這樣,已經(jīng)掉進這個溝里了),對于新手,我可能會加一行“注意邊 界值”,可能做我的新手可能比較“倒霉”哈,我一不會告訴你用例設計的方法,二不會告訴你用例執(zhí)行思路,前者我認為是有志干這行的必修課,必須自己主動掌 握,我僅能帶你入門而已,至于后者則靠自己不斷測試不斷總結(jié),每個人都不一樣,找到那個最適合自己的。還有一個不好的地方就是太詳細的用例給人的感覺就是 我不需要思考了,反正設計者已經(jīng)想得很詳細了,導致編寫用例的時候絞盡腦汁,執(zhí)行的時候完全可以被程序替代了。那樣也很難發(fā)現(xiàn)這個系統(tǒng)的隱藏的缺陷了。當 然對于在設計階段就想的很詳細的用例來說,不會因為測試人員的疏忽而導致潛在缺陷被遺漏,畢竟用例規(guī)范了測試人員的行為。
用例編寫過程中不明白的問題可以先記錄下來,在用例編寫完成后,將所有不明白的地方匯總,提交需求確認,待確認后補充對應的內(nèi)容。
測試人員的工作是規(guī)范別人(需求、開發(fā))的工作,我們自己的工作也必須規(guī)范,必須接受項目組其他人員的檢查,用例評審就是一個很好的方式,需求、開發(fā)和測試在一起討論達成最后的結(jié)果。測試人員負責修補其中不滿足的地方。
接下來就是執(zhí)行用例的習慣了,個人喜歡在執(zhí)行之前通讀該功能模塊的所有用例,然后統(tǒng)計下需要測試的功能點,對執(zhí)行的順序初步排序,這樣可以能提高效率, 不至于顧此失彼。譬如有6個用例,分別是對對象的增、改、刪和對對象關(guān)系的增、改、刪,編寫用例時的一般按照這個順序下來,執(zhí)行的時候如果這樣的話那明顯 增加了工作量,改成對象的增、改完成后執(zhí)行對象的關(guān)系,關(guān)系執(zhí)行完成后刪除對象。既完整的執(zhí)行了用例、測試了功能,又能做到效率最大化。
記得一位資深的工程師說過:在一個產(chǎn)品的迭代兩輪過后,不加入新功能的情況下,僅憑用例是很難發(fā)現(xiàn)問題的。而實際過程中也確實如此,正是有鑒于此,個人 比較傾向測試用例只要設計的能夠完整覆蓋系統(tǒng)的功能點即可,至于新手的執(zhí)行的問題,測試用例是為了規(guī)范測試人員的工作,將無限的測試工作有限化,并且方便 管理和復用,好像并不具備新手入門手冊這個功能(可以通過其他方式解決,不必在測試用例中過分遷就他們)。
權(quán)且一家之言,不對之處歡迎討論。
在此之前我搜集一些關(guān)于
測試用例的知識,后來在我們的QQ群里專門定了一期討論,來探討測試用例,畢竟這是一個很大的話題,很難做到面面俱到,但我會盡量全面,用通俗的語言來說測試用例。
-------------------------------------------------------------------------------------------------------------------------
注:我們這里要說的測試用例指功能測試用例。
一、什么是測試用例?
測試用例是為某個特殊目標而編制的一組測試輸入、執(zhí)行條件以及預期結(jié)果,以便測試某個程序路徑或核實是否滿足某個特定需求。
通俗的講:就是把我們測試系統(tǒng)的操作步驟用按照一定的格式用文字描述出來。
二、寫測試用例有什么好處?
理清思路,避免遺漏
這里是我們認為最重要的一點,假如我們測試的項目大而復雜,我們可以把項目功能細分,根據(jù)每一個功能通過編寫用例的方式來整理我們測試系統(tǒng)的思路,避免遺漏掉要測試的功能點。
跟蹤測試進展
通過編寫測試用例,執(zhí)行測試用例,我們可以很清楚的知道我們的測試進度。
歷史參考
在我們所做的項目中,也許會有很多功能是相同或相近的,我們對這類功能設計了測試用例,便于以后我們遇到類似功能的時候可以做參考依據(jù)。
重復性
我們測試一個系統(tǒng)不是一個人測一遍就算測完的,需要多人反復的進行測試,那么我們就需要測試用例來規(guī)范和指導我們的測試行為。
告訴領(lǐng)導,這事俺干過,不然別人怎么知道你測沒測,測的全面不全面,拿測試用例給他們看唄!俺就是照著這個干活,呵呵!
三、測試用例的方法
好吧,咱知道啥是測試用例了,也是知道為什么要寫測試用例了,那到底應該怎么寫?無從下手啊。我們在寫測試用例之前,先學習幾種方法,它是我們寫測試用例的指導思想。
1、等價類劃分
在某個輸入域的子集合,在該子集合中,各個輸入數(shù)據(jù)對于揭露程序中的錯誤都是等價的。假如有一個輸入框要求輸入1-10000個數(shù),我們不可能用每一個 數(shù)去試,我們輸入5 和輸入6去驗證和揭露輸入框的錯誤可以看做是等價的。那么這個時候我們就可以隨機的抽取一些數(shù)據(jù)來進行驗證。如:10 、99、7777......
等價類分:有效等價類和無效等價類
輸入框要求輸入1-10000的數(shù)
有效等價類:可以輸入1-10000之間的數(shù)來驗證,如:2、5、99、8495......
無效等價類:可以輸入1-10000之外的任意字符驗證,如:20000、字母、下劃線、特殊符號、空格、回車.....
2、邊界值
邊界值是對等價類的補充,測試工作經(jīng)驗告訴我們,大量的錯誤是出在輸入輸出的邊界價上。我們還拿上面的例子,一個輸入框要求輸入1-10000之間的數(shù)。我們要測它有沒有超出這個范圍,如:0、-1、-2、1000、10001.....等等,來判定是否超出了我們的范圍。
3、因果圖 因果圖方法最終生成的就是判定表,它適合于檢查程序輸入條件的各種組合情況。舉個例子:原因:A=0,B=0,結(jié)果我就可以判定:A=B。確切 的說他是一種因果關(guān)系思想。它會無形中指導這我們的測試。當然了,我們?yōu)榱艘悦膺z漏,可以把系統(tǒng)中的因果關(guān)系用圖畫出。不過系統(tǒng)大而復雜的話就是個體力活 了。呵呵。
4、錯誤推測法
基于經(jīng)驗和直覺推測出系統(tǒng)可能存在的錯誤,從而有針對性的設計測試用例的方法。
5、其它
設計測試用例的方法有很多,我們常用就上面幾種,其它的方法還有:狀態(tài)遷移圖、流程分析法、正交驗證法等等。
四、測試用例的格式與要素
一個測試用例應該包括:編號,標題,測試場景,測試步驟,預期結(jié)果。
當然還可加入一些它選項,如:優(yōu)先級、測試階段....

注:上面的格式取自《微軟的軟件測試之道》,它并不一定適合你,我只是讓大家對測試格式有個了解。
關(guān)于測試用例的存放管理:
1、項目管理系統(tǒng)自帶的用例管理,一般用例會與項目掛鉤,有固定的格式,搜索、修改等功能,使用起來非常方便。如:禪道項目管理、QC、bugfree 等等都帶的有用例管理功能。
2、通過world\Excel文檔形式管理,這樣的好處就是自己定義測試用例的格式。
-----------------------測試用例例子--------------------------------------------------------
基礎知識了解的差不多了,下面來看一個具體的測試用例。我們會有更深刻的認識。

注:這不是一個完整的測試用例,格式也不是固定必須這樣的,你們可以根據(jù)自己的需求編寫設計測試用例。
------------------------------------我們還需要知道的,關(guān)于測試用例的-------------------------------
一、我們在什么時候可以設計測試用例?
當根據(jù)客戶的需求整理出項目需求分析文檔時,我們就可以根據(jù)需求文檔來編寫測試用例了。但是,一般我們(國內(nèi)大多小公司)項目需求文檔都非常“簡陋”,所以,很難根據(jù)需求文檔設計測試用例。
我們只有等到項目開發(fā)人員把項目開發(fā)出來,給我們系統(tǒng)文檔、部署環(huán)境、數(shù)據(jù)庫結(jié)構(gòu)(如果系統(tǒng)牽涉到數(shù)據(jù)庫的話),我們根絕這些文檔來設計測試用例。
二、測試用例的評審與更新
我們設計的測試用例設計完成之后,是否完整?是否符合系統(tǒng)?符合客戶要求?對用例做一個評審是必不可少。關(guān)于評審的方式,不同的公司有不同的流程。
我們編寫的測試用例也不是經(jīng)過評審之后就不變了,隨著需求的變更、功能的改進,測試用例當然也需要更新和變動。
三、什么情況下不適合寫測試用例
文件時間
如果一個功能我很快就測試完了,而且只需要測試一遍,但我們設計測試用例時卻比較麻煩,花時間也長。這個時候就沒必要編寫測試用例了。
需求變動大且頻繁
需求的功能變動非常頻繁,而且變動很大,之前編寫的測試用例根本沒法使用,必須要重新編寫,這個時候也沒必要去設計測試用例了。
項目時間不允許
這一項是不太厚道的做法,如果不是急需交付客戶的話,盡量不要這樣做;當然了,如果只是給客戶展示或試用,可以在之后進行補充和完善測試用例。
不要編寫不完整或別人看不懂的測試用例,那樣就沒有意義了。
==========================================個人閑聊內(nèi)容,歡迎指正===================================
四、停止軟件測試的標準。
語句覆蓋最低不能小于80%,測試需求覆蓋率達到100%,測試用例覆蓋率達到100%,一、二級缺陷修復率達到100%,三、四級修復率達到80%。
(上面一句是再網(wǎng)上找的,不是標準,只是個參考)
bug等級:
一級:非常嚴重的bug
二級:嚴重的bug
三級:一般性的bug
四級:建議性問題
五、關(guān)于探索性測試
完全的執(zhí)行測試用例時一件非常枯燥的事情,個人在執(zhí)行測試用例時會做一些,其它的非常規(guī)性的操作,看系統(tǒng)是否會有相應的處理和提示。我的一部分bug就是再這種非常規(guī)操作下發(fā)現(xiàn)的。
當然了真正的探索性測試需要對產(chǎn)品的深入了解,以及軟件開發(fā)技術(shù)有一定的深度和寬度。姑且把我們的探索性測試看成是瞎搗鼓吧!呵呵。
六、交叉測試
有木有發(fā)現(xiàn),當我們第一遍測試系統(tǒng)時,會非常認真,但要我們測試第二遍時,我們不愿意像第一次那樣認真的去測了,這不能說明我們不負責,而是每個人都有的心理現(xiàn)象。這個時候,我們可以和其它測試人員交換功能來測試,提高效率,而且更容易發(fā)現(xiàn)問題。
七、測試的目的
1、我們讓它做的它必須會做。
2、我們不讓它做的它必須不會做。
可能你會發(fā)現(xiàn)有附加功能的時候,就是客戶沒有要求,我們加了這樣的功能,可能加了這點功能系統(tǒng)看上去會更好。這時怎么辦?算問題么?
作為開發(fā)人員,中規(guī)中矩的做東西最好,如果真的有非常好的功能要加的話,需要和客戶溝通,然后寫到需求里。畢竟多一點功能多一點風險。呵呵
作為測試人員,凡是不符合需求文檔的都需要當問題點提出。責任分明,以免后續(xù)麻煩。
----------------------------------------------------
修改:
1、測試用例的格式的要素,去掉“實際結(jié)果”
2、關(guān)于測試方法“等價類劃分”的解釋。
謝謝“zdd”朋友的糾正。:)
作為黑盒測試的一個重要階段,功能測試毋庸置疑是不可缺失的。功能測試的相關(guān)話題很多,無論是測試的形式,例如手動測試和自動化測試,還是測試方法,例如數(shù)據(jù)驅(qū)動和關(guān)鍵字驅(qū)動,都有大量的研究文章。我這篇博文里卻打算從國別不同的角度來討論一下功能測試的差異,原創(chuàng)文章可能有一些謬誤的地方,請讀者指摘。
日式循規(guī)蹈矩
日本人給世界其他民 族的印象是做事認真嚴謹,對待問題一絲不茍,犯了錯誤按嚴重程度該下跪的下跪,該剖腹的剖腹。他們的這種一貫行事方式也帶到了軟件行業(yè),而軟件行業(yè)的摩爾 定律,技術(shù)的日新月異,代碼、框架的多變,都似乎與他們的性格格格不入。日本制造的東西一向以堅固耐用著稱,給我映像最深的一個細節(jié)是,在日本工作的時候,發(fā)現(xiàn)他們的垃圾袋居然都堅固無比,用來逛超市買東西拎重物也是屢用不壞。然而,對于遵從摩爾定律的計算機行業(yè)來說,投入大量的精力來盡可能的發(fā)現(xiàn)bug以及解決問題是否很值,這真的是有待商榷的問題。
但,話雖如此,日企采用的測試用例的設計方法還是非常值得我們學習的,這其中又首先要提一下要因分析法(網(wǎng)上有些說法把魚骨圖等同于要因分析法,我并不贊同此說,后面會有詳述)。
要因分析法的精髓在于,以產(chǎn)品的某一特性為因子,以這個特性的不同表現(xiàn)(根據(jù)等價類劃分、邊界值分析等方法)為狀態(tài),一一列舉后,采用二維組合的方式來確定測試用例。
下面我舉一個簡單的例子“我打算從南京去北京”來說明。

表1
這即是一張簡單的要因表,值得注意的是,因子和狀態(tài)的確定是必須規(guī)定范圍的,而這個范圍在這個例子中則為“正常人的思考”范圍。譬如說,“交通方式”我 沒有寫“步行”,因為這不符合常人從南京去北京的思考方式,當然有人為了挑戰(zhàn)吉尼斯紀錄,這里非要采用步行方式從南京前往北京,那么狀態(tài)里添加這項顯然是 可以的。
此外,因子和狀態(tài)的另一個隱性的確定方針為詳細度,這個度如何把握,我們可以從下表來理解:

表2
表2將表1的“交通方式”進一步細化,此時狀態(tài)的選擇將進一步增多。如果要求詳細度更加提高,例如因子為“動車”,那么可以加上狀態(tài)為“一等座”和“二等座”,這種組合很靈活,取決于我所需的詳細級別。
要因確定之后,便是組合,以表1所列要因為例,二維組合有下列共18種:
飛機-晴-白天,飛機-雨-白天,飛機-雪-白天,飛機-晴-夜晚,飛機-雨-夜晚,飛機-雪-夜晚 火車-晴-白天,火車-雨-白天,火車-雪-白天,火車-晴-夜晚,火車-雨-夜晚,火車-雪-夜晚 汽車-晴-白天,汽車-雨-白天,汽車-雪-白天,汽車-晴-夜晚,汽車-雨-夜晚,汽車-雪-夜晚 |
對于表2來說,二維組合則有2*4*2*3*2=96種,貌似有點多,當然你想分析的越詳細,那么組合數(shù)量自然會相應的增加。
回到表1得出的18種用例,假如我的通過條件是從南京到北京的時間越短越好,在實際的外界環(huán)境下(例如晴天,預期花費有限額),這18個用例中,就會出 現(xiàn)有的測試通過,有的測試失敗的情況了。在不同的實際外界環(huán)境下,測試通過的情況還會發(fā)生變化(例如下雪天,飛機會發(fā)生班次延誤)。
要因分析法的好處在于“事無巨細,滴水不漏”,壞處在于過程“繁瑣冗長,枯燥乏味”。即使如此細致的要因分析,依然存在一定的用例遺漏的風險, 這個風險來自于對因子和狀態(tài)潛在的考慮不周。隨著詳細級別要求或者系統(tǒng)復雜度的提高,使用要因分析法組合出詳盡的測試方案則成為了QA的一種折磨,每一個 QA遇到復雜的測試對象,都將成為酷刑下折翼的天使,欲哭無淚的在心中默默吶喊“坑爹啊”(因此要對要因法做一定程度的改良,如何改良,后文將詳述)。
前文伏筆“要因分析法不是魚骨分析法”,魚骨分析法的另一個更加正式的書面稱呼是“根本原因分析法”(日本管理大師石川馨先生發(fā)展出來的,故又 名石川圖)。根本原因分析法同樣有著折磨常人的地方(題外話:為什么日本人使用的方法總是那么坑爹?),它要求分析者們集中精力尋求發(fā)生問題的任何一種可 能性(頭腦風暴),將這些可能性繪制在魚骨圖上,再尋求所有可能性的根源,其本質(zhì)上不是一種用于設計測試方案的方法(僅僅用于追溯問題,例如發(fā)現(xiàn)bug后 追溯引起bug的根源)。關(guān)于根本原因分析法的討論,由于和本文的重點有偏離,因此不做進一步描述,題外話就此打住。
歐美式頭腦風暴
眾所周知,歐美企業(yè)的風格是強調(diào)人性和自由,對于測試來說,自然不可能采納日式那種條條框框的做法。測試方案設計的基本方法和準則,例如邊界值 分析、等價類劃分、因果圖等,被QA們牢牢的記在心中,功能測試方案設計時,根據(jù)需求分析或用戶手冊,眾人在一起集中進行頭腦風暴,此時包括RD也將參與 進來,對于測試合理或者不合理的地方提出建議。因此,方案設計的時候,更強調(diào)的是經(jīng)驗和閱歷以及對需求的關(guān)注程度。測試方案設計是有偏重的,對于一些重要 的feature強烈關(guān)注,用例也將根據(jù)feature的重要性而詳略有別。
頭腦風暴式的測試用例設計的輔助工具往往以思維導圖為主,還是以“我打算從南京去北京”為例,其中一種思維導圖設計如下:

思維導圖的靈活性很高,因此設計出的導圖每次都會有所不同,跟隨著與會者偏重點的不同而產(chǎn)生不同的設計方案。
好比歐洲的菜肴大多以整塊烹制為主,而中國人的菜肴則基本上都是切碎的。這是因為受限于傳統(tǒng)使用的餐具。而測試方案的設計方法也同樣受到西方和 東方文化差異的影響,例如Agile首先在歐美出現(xiàn)并迅速得到推廣和應用,而Agile絕對不會首先出現(xiàn)于日本。對于頭腦風暴式的測試方案設計,這頗有 Agile的風格,項目參與者進行充分的思想交流,QA們將每一個閃現(xiàn)于頭腦的想法都記錄在案,同時根據(jù)別的QA和RD的建議來不斷地修正或彌補方案,直 到完成設計。
這種設計方法的好處是擁有很好的靈活性,且可以避免精力被耗費到旁枝末節(jié)上。用例可以是很多步驟組合起來的(表現(xiàn)為思維導圖的層次較多),通過 一個用例測到多個feature,這在進行具體的自動化測試代碼編寫時可以節(jié)省大量勞動力。由于交流足夠充分,某些不易被想到的測試用例也可以被挖掘出來 (好比繪制清晰的思維導圖)。然而,這種方法的缺點也是顯而易見的,某些測試用例有可能在頭腦風暴中被忽視或遺忘,且受限于人的思維的不嚴密性,未設計在 案的測試用例,往往也沒有人會關(guān)注到“為什么這些測試用例不用測”這個問題。
歐美與日式的比較
其實從上述的兩個篇章,我已經(jīng)闡述了二者之間的差異。日式苦行僧般的要因分析法,幾乎可以遍歷窮舉所有可能的組合方式(除非因子有遺漏),設計 完畢后,到了具體測試實施階段,無論是手動測試還是自動化測試,對于QA來說,都是一個比拼耐力的考驗,測試用例數(shù)動輒過千,大量的測試用例之間甚至只有 細微的差別。QA將完全體驗不到創(chuàng)作的樂趣,轉(zhuǎn)而成為一名不折不扣的體力勞動者。一個測試對象的測試周期也被大大拉長,所需的人月數(shù)也很多。完成這些繁瑣 的工作之后,測試對象將趨于完美,細微的bug也將被找出并修正。此時不排除測試對象可能已經(jīng)是一個落后的甚至被淘汰的產(chǎn)品了。當然,這對于用戶忠誠度極 高的日本人來說,這不算什么問題,他們不厭其煩的強調(diào)產(chǎn)品品質(zhì),但是對于這顆星球上的其他民族來說,大多寧可忍受一些小bug,也不愿使用一個過期的技術(shù) 落后的產(chǎn)品。
對于歐美式的測試設計,顯然比較契合當前的飛速發(fā)展的計算機業(yè),但產(chǎn)品中留下的bug數(shù)量往往也會比日式測試法多的多。這尤其表現(xiàn)在產(chǎn)品的一些局部的、次要的功能上,這些功能往往將成為bug集中營。
兩者融合的思考
歐美與日本,兩者采用的方法各有長處。一者的缺點往往恰是另一者的長處。那么該如何從兩者中取長補短,去粗取精以至互相融合呢?這也是我在工作中一直不斷思考的一個問題。
我認為有一種可行的解決方案可以按照如下的做法進行:
1、列出要因表,因子和狀態(tài)的列舉方式可以采用思維導圖進行
2、根據(jù)deadline來劃分測試的詳略程度,制訂測試計劃
3、頭腦風暴,將要因表的二維組合放在參與者的頭腦中進行
4、在列舉測試用例的同時,對不測的用例也要追究一下不測的原因
5、歸納測試用例之間的共性,對于差別較小的測試用例,要考慮如何整合到一起,對于可以串行的用例,要考慮是否可以合并為一個多步驟的用例
通過以上5個步驟,我想既可以避免要因分析法的要因組合階段帶來的讓人抓狂的繁瑣和用例數(shù)量過多的缺陷,又可以避免頭腦風暴帶來的某些用例的考慮不周。
是否還有其他更好的取長補短的方法呢?這個問題將依然縈繞在我的日常測試工作中,吾將上下求索,并期待您對本文的看法能夠給予我茅塞頓開的啟迪。
第二章 單元測試的基本概念和核心技法
2.1 良好的單元測試——定義
我們已經(jīng)了解了程序員需要單元測試,下面我們來給單元測試作一個完整的定義:
● 定義: 單元測試是一段自動執(zhí)行的代碼,它調(diào)用被測類或被測方法,然后驗證關(guān)于被測類或被測方法邏輯行為的假設確實成立。單元測試幾乎總是用單元測試框架(unit testing framework)來寫就的,單元測試是易于寫就、執(zhí)行快速、完全自動化、值得依賴、易于閱讀并易于維護的。
這個定義有點長,但是它卻包含了大量重要信息:
● 單元測試的測試重點是被測類或被測方法的邏輯行為。所謂“邏輯行為”,指的是含有諸如判斷、循環(huán)、選擇、計算或其他決策過程的代碼。
● 單元測試框架是輔助程序員寫就單元測試的利器。
● 單元測試的代碼本身,同被測代碼一樣,也應該是值得依賴、易于閱讀并易于維護的。
有了單元測試的定義,我們來看看有關(guān)單元測試的幾個基本概念,這些基本概念會在后面的章節(jié)中反復出現(xiàn)。
● 被測類(Class Under Test)和被測方法(Method Under Test):顧名思義,就是測試代碼所操練的類或方法。
● 測試類(Test Fixture)和測試方法(Test Method):負責操練被測類和被測方法。Test Method一般都是Test Fixture的成員函數(shù)。
● 測試運行器(Test Runner):負責自動執(zhí)行測試類中的測試方法。Test Runner可以是命令行界面的,也可以是GUI的。
2.2 進行單元測試的核心技術(shù)和核心手法
2.2.1 核心技術(shù)
前面已經(jīng)講到,單元測試所針對的目標是“單個”類或“單個”方法(這里的“方法”指C++中的自由函數(shù))。這表明我們在單元測試中要做的主要任務是創(chuàng)建出被測類的對象,并調(diào)用該對象的被測方法。但是我們都知道,一個類幾乎不可能完全不依賴于其他類。這種類之間的依賴會導致我們無法順利地將一個被測類納入單元測試覆蓋這下,因為單元測試需要的是對被測類這一個類的測試,而不是同時測試被測類和它的合作者類。

因此,我們在把ClassUnderTest納入單元測試時,也就必須先把ClassUnderTest與CollaboratorClass之間的依賴“打破”,這個過程稱為“解依賴”(dependency-breaking)。解依賴就是進行單元測試的核心技術(shù)。解依賴的目標是希望能把不可控的CollaboratorClass替換成由我們控制的偽合作者類(FakeCollaboratorClass),并使被測類能方便地選擇是依賴于真實的合作者類還是偽合作者類。對于產(chǎn)品代碼,被測類依賴的是真實的合作者類,而在單元測試中,被測類依賴的是由我們控制的偽合作者。

在單元測試代碼中使用可控的FakeCollaboratorClass,這給我們帶來了兩個便利:
● 我們可以通過FakeCollaboratorClass向ClassUnderTest返回任何我們指定的結(jié)果。
● 我們可以通過FakeCollaboratorClass來感知ClassUnderTest所做的動作。
這實際上就是FakeCollaboratorClass的兩種表現(xiàn)形式:
● Stub:用于向ClassUnderTest返回我們指定的結(jié)果。
● Mock:用于感知ClassUnderTest的行為。
我們明白了“解依賴”是單元測試的核心技術(shù)。那么具體怎樣實現(xiàn)解依賴呢?下面我們就來介紹4種相關(guān)的核心手法,其中前2種與CollaboratorClass有關(guān),后2種與ClassUnderTest有關(guān),這4種手法對于解決絕大多數(shù)的解依賴問題都適用。
2.2.2 “接口提取”和“Virtual and Override”
“接口提取”手法是對CollaboratorClass提取出一個抽象接口CollaboratorService,然后讓CollaboratorClass和FakeCollaboratorClass都去實現(xiàn)這個接口,而ClassUnderTest則由直接依賴CollaboratorClass轉(zhuǎn)向依賴于抽象接口CollaboratorService, 如下圖所示。

實際上,“接口提取”手法是一種非常好的手法,它使得我們的代碼遵循“依賴抽象原則”,遵循這個原則的軟件具有較好的靈活性,這是具有可測試性的軟件也具有較好的設計的一個佐證。
而“Virtual and Override”手法則是使CollaboratorClass中的被依賴方法成為virtual,然后讓FakeCollaboratorClass去公有繼承CollaboratorClass,并且override那些虛函數(shù),從而替換掉 CollaboratorClass中的行為。這種手法如下圖所示。

總體上講,我們推薦優(yōu)先使用“接口提取”手法,因為這將使代碼遵循“依賴抽象原則”,從而使軟件更好地應對今后的變化。但是,“Virtual and Override”方法也是有其一席之地的,我們后面會看到例子。
2.2.3 “參數(shù)注入”和“Extract and Override”
在ClassUnderTest這邊,對CollaboratorClass的依賴的產(chǎn)生方式也可以劃分成兩類:
依賴是通過方法參數(shù)傳入的,這種形式的依賴被稱為“參數(shù)注入”式依賴(parameter injection dependency)。參數(shù)注入式依賴是一種耦合度較低的依賴產(chǎn)生形式,因此對ClassUnderTest的影響不大,一般最多只需要把方法的簽名由直接依賴CollaboratorClass改成依賴接口CollaboratorService。
依賴是在被測方法的方法體內(nèi)部產(chǎn)生的,這種依賴被稱為“隱藏式”依賴(hidden dependency)。隱藏式依賴有多種表現(xiàn)形式:
● 直接創(chuàng)建CollaboratorClass對象作為局部變量或成員變量。
● 通過一個工廠方法來產(chǎn)生CollaboratorClass對象。
● 通過一個工廠類來產(chǎn)生CollaboratorClass對象。
隱藏式依賴是一種耦合程度較高的依賴,因此是我們著重需要“打破”的依賴。一種方法是把隱藏式依賴轉(zhuǎn)變成參數(shù)注入式依賴,我們將在后面的小節(jié)中看到這種方法的應用。而另一種方法,則是使用“Extract and Override”手法,即:我們給ClassUnderTest引入一個virtual的工廠成員函數(shù),來返回一個CollaboratorClass對象的引用,然后對ClassUnderTest派生出一個子類,在該子類中override這個工廠成員函數(shù),讓它返回一個FakeCollaboratorClass對象的引用,如下圖所示。

這里的TestingClassUnderTest被稱為“測試用子類”(Testing Subclass)。這時,在Test Fixture中被實例化的其實就是測試用子類,而不是被測類本身。
以上我們研究了進行單元測試所需要的核心技術(shù),以及4種最常用的核心手法,這些手法足以應付絕大多數(shù)的情況,但是,仍然有一些特殊情況需要我們特別注意,我們從下一節(jié)開始,以Q&A的形式,討論這些特殊情況。
2.3 應付構(gòu)造函數(shù)中的隱藏式依賴
當ClassUnderTest中出現(xiàn)隱藏式依賴時,最常用的有兩種手法來打破這種依賴。我們分別來看一下。
2.3.1 轉(zhuǎn)變成參數(shù)注入式依賴
對于像C#和Java這樣的語言,由于它們支持在一個構(gòu)造函數(shù)中去調(diào)用另一個構(gòu)造函數(shù),因此可以很方便地增加一個構(gòu)造函數(shù),把隱藏式依賴轉(zhuǎn)變成參數(shù)注入式依賴。下面的UML圖闡釋了這種手法。

而對于C++,由于它沒有提供在構(gòu)造函數(shù)中調(diào)用另一個構(gòu)造函數(shù)的功能,因此通常的作法就是把公共的的初始化代碼放入一個init()私有方法中去,讓不同的構(gòu)造函數(shù)去調(diào)用init()方法。

2.3.2 使用“調(diào)包方法”
還可以考慮給ClassUndertTest引入一個“調(diào)包方法”,使得測試類和測試方法可以很方便地將合作者類“調(diào)包”成偽合作者類。這里的“調(diào)包方法”本質(zhì)上就是一個setter方法,但是為了表明這個特殊的setter方法只應該被測試類和測試方法調(diào)用,我們一般給調(diào)包方法命名為SupersedeCollaborator()。下圖就是一個演示。

這里必須要提醒的是,在C++中使用這個手法時必須注意在“調(diào)包方法”中不能引起資源泄漏,也就是說,必須先釋放掉原有的合作者對象,再以偽合作者替代之。
2.4 怎樣測試ClassUnderTest中的private方法?
如果要測試一個類的private方法,答案的總體思路是:適當打破訪問權(quán)限。也就是說,我們可以把private方法聲明成protected方法,然后對ClassUnderTest進行派生,得到一個“測試用子類”,在該“測試用子類”中聲明一個public方法,該方法是ClassUnderTest中的protected方法的代理。這種手法可以用下圖來表示。

2.4 應付“全局依賴”
所謂“全局依賴”,是指被測類或被測方法所依賴的合作者是一個“全局單件”,包括C#/Java/C++中的“單件類”(Singleton)和C++中的全局變量和自由方法(實際上, Singleton可視為全局變量在面向?qū)ο笳Z言中的變種)。我們下面來看看怎么應對這些情況。
2.4.1 使用“封裝全局引用”手法來解除對全局變量和自由方法的依賴
要打破對全局變量和自由方法的依賴,其實基本思想就是:把它們納入到一個類中,讓它們成為一個類的成員變量和成員方法。一旦把全局變量和自由函數(shù)封裝進了某個類,那么我們就可以繼續(xù)利用2.2.2節(jié)提到的兩種手法來引入偽合作者了。

2.4.2 使用“靜態(tài)調(diào)包方法”來解除對單件類的依賴
單件類往往具有3個特點:
(1)單件類的構(gòu)造函數(shù)通常被設為private。
(2)單件類具有一個靜態(tài)成員變量,該成員變量持有該類的唯一一個實例。
(3)單件類具有一個靜態(tài)方法,用來提供對單件實例的訪問,通常該方法名叫g(shù)etInstance()。
由于單件類被設計成強制性地在整個程序中只有一份,因此要偽造它比較困難。我們推薦使用“靜態(tài)調(diào)包方法”手法。這個手法的本質(zhì)是適當打破單件類的單件性約束,把單件類的構(gòu)造函數(shù)改為protected,使我們能夠從單件類派生出一個Fake子類,然后為單件類引入一個靜態(tài)調(diào)包函數(shù)SupersedeInstance(),以允許單件類中的靜態(tài)成員變量被“調(diào)包”成Fake子類對象。下圖表明了這一手法。

同樣必須強調(diào)的是,在C++中的使用這個手法的時候,必須保證沒有資源泄漏。
2.5 如果CollaboratorClass本身就處于繼承體系中,怎么辦?
先設想一下CollaboratorClass本身存在基類的情況,如下圖所示。

如果使用“Virtual and Override”手法來偽造合作者類,那么不存在任何問題,我們可以用下面的圖來表示。

另一方面,如果想使用“接口提取”手法的話,那么一種比較好的策略是使用“外覆類”(Wrapper Class),如下圖所示。

2.6 當CollaboratorClass是標準類庫的一員時,怎么辦?
有些時候,我們的被測類所依賴的是特定操作系統(tǒng)(Windows, Unix, 或Mac)、特定標準規(guī)范(.NET,或J2EE)、特定函數(shù)庫或類庫(如POSIX API和Win32 API)及特定用戶界面(CUI或者GUI)所提供的功能時,這實際上是引入了對特定平臺的依賴性,而這往往都是在提示我們:應該加入一個更高層次的抽象(也即一個間接層,indirection),從而將這種對特定平臺的依賴隱藏到這個抽象之后。換句話說,我們應該引入一個接口,來使我們的代碼具有平臺無關(guān)性,如下圖所示。

2.7 怎樣測試抽象接口?
假設我們的系統(tǒng)中定義了一個抽象接口ServiceInterface,系統(tǒng)中有兩個類(分別是ServiceImpl1和ServiceImpl2)實現(xiàn)了這個接口。現(xiàn)在,我們希望為ServiceInteface抽象接口編寫一個通用的測試類,這個測試類不僅能測試當前已經(jīng)實現(xiàn)該接口的類,而且可以不加修改地應用于將來實現(xiàn)ServiceInteface接口的類。應該怎么辦呢?下圖展示了一種可能的方案。

上圖中,ServiceInterfaceTestFixture測試類中的測試方法都是基于ServiceInterface來進行測試的,不依賴于其具體實現(xiàn)類。這樣就保證了僅測試抽象接口所定義的行為。當將來系統(tǒng)引入ServiceInteface的新的實現(xiàn)類時,只需要從ServiceInterfaceTestFixture類再派生出一個新子類,并實現(xiàn)createServiceInstance()方法來創(chuàng)建相應的對象即可。
第一章 為什么使用單元測試 1.1 程序員的工作——修改軟件
修改既有代碼是程序員謀生的手段。但是為什么我們需要去修改軟件呢?修改軟件有以下4個主要起因:
● 修正bug
● 添加新特性(feature)
● 改善設計
● 優(yōu)化資源使用
這4項都與軟件的“行為”密切相關(guān),見下表。
| 軟件的既有行為 | 軟件的新行為 |
修正bug | 改變軟件的既有行為 | 增加新行為 |
添加新特性 | 保持軟件的既有行為,完全不修改既有代碼 | 無 |
改善設計 | 保持軟件的既有行為,但軟件的可維護性得到提升 | 無 |
優(yōu)化資源使用 | 保持軟件的既有行為,但軟件的性能得到提升 | 無 |
通過這張表格我們看出:只有在修正bug時,我們才需要改變軟件的既有行為,而在其他情況下,我們都需要保持住軟件的既有行為。如果我們在改善設計,優(yōu)化,或添加新特性時改變了軟件的既有行為,那我們實際上是給軟件引入了bug。
可是程序員的工作就是修改軟件,所以我們有很多的“機會”給軟件引入bug。有什么辦法能讓我們的生活輕松一點,而不用因為修改代碼引入bug而擔驚受怕嗎?
1.2 軟件夾鉗——測試
我們已經(jīng)看到:在大多數(shù)情況下,我們希望對軟件所做的改動不會改變系統(tǒng)的既有行為。即使是對于修正bug這種情況,我們也希望一旦bug被修正,那么修正后的正確行為能夠得到保持,而不會被再之后的代碼修改所改變。怎樣做到這一點呢?
讓我們這樣想想:如果我們能在對代碼進行改動之前,用一種“軟件夾鉗”(software vise)來固定住軟件的既有行為,那么我們就可以放心大膽地去修改代碼了。那么,又是什么可以來充當“軟件夾鉗”呢?答案是:測試。我們可以這樣想一 下:當一段代碼被一組良好的測試所覆蓋時,我們就可以放手去修改這段代碼,并在修改完成之后立即運行這組測試,來驗證我們的修改并沒有改變既有行為而引入 bug。如果確實改變了既有行為,那么測試就會明確無誤地發(fā)出警報。由此可見,測試就是程序員所需要的“軟件夾鉗”。
1.3 單元測試與集成測試之爭
我們已經(jīng)知道了“測試”就是程序員所需要的“軟件夾鉗”。但是測試分為單元測試和集成測試,程序員需要哪種測試呢?讓我們來分析一下程序員需要什么樣的測試,這樣或許我們就能知道程序員應該更偏向于哪種測試了。
● 程序員需要的測試應該是能幫助程序員定位錯誤的,這樣程序員才會真正地從測試中得到“實惠”。
● 程序員需要的測試應該是很容易執(zhí)行的,最好是只需點擊一個按鈕或鍵入一條命令,這些測試就能運行,而無需費時費力地去搭建測試環(huán)境及準備測試數(shù)據(jù)或儀器。
● 程序員需要的測試應該是運行速度很快的,最好能在幾分鐘內(nèi)完成,這樣程序員才能快速地得到反饋,采取下一步動作。
● 程序員需要的測試應該是易于寫就的,而不愿意對代碼基大動干戈,這樣程序員才會愿意去寫這些測試。
● 程序員需要的測試應該是自動化的,可重復的。這樣程序員才會愿意重復多次地去運行這種測試。
比對程序員的需求,我們可以發(fā)現(xiàn)集成測試往往不能滿足程序員的這些需求:
● 集成測試由于涉及多個模塊,因此往往不能提供準確的錯誤定位信息。
● 集成測試的執(zhí)行時間一般較長(小時級),這不能給程序員帶來快速反饋。
● 集成測試可以自動化執(zhí)行,但前提是把測試環(huán)境和測試數(shù)據(jù)等事先準備好。
● 集成測試由于不太容易寫就,通常不是由程序員寫就,而由專門的測試人員寫就。
相反,單元測試,尤其是良好的單元測試,恰恰正是程序員所需要的那種測試:
單元測試針對單個類或單個方法,能很有成效地幫助程序員準確定位問題所在。
單元測試應該是執(zhí)行時間很短的,全部執(zhí)行也只需5到10分鐘,程序員正好可以去喝喝咖啡。
單元測試是一種“虛擬”測試,重在測試代碼邏輯,因此一般不需要真實測試環(huán)境和測試數(shù)據(jù)的支持。
單元測試是很容易寫就的,尤其是有單元測試框架的幫助時。
由此可見,對于程序員而言,需要的是單元測試。程序員使用單元測試來充當軟件夾鉗,并在修改代碼時獲得快速反饋,從而更有信心地投入到修改軟件的工作中。
1.4 進行單元測試的其他好處
我們已經(jīng)知道了單元測試帶來的一個好處:它可以充當程序員的“軟件夾鉗”,在程序員修改軟件的過程中給予程序員快速的反饋,幫助程序員定位問題,避免引入bug。單元測試就只有這一個好處嗎?不是的。下面我們就來看看引入單元測試帶來的其他好處。
1.4.1 單元測試是代碼的“活文檔”
讓文檔及時反映軟件設計和代碼的最新情況,這是一個頗有挑戰(zhàn)性的問題。一種較好的思路是:使用“內(nèi)部”文檔,即把文檔同代碼“拴”在一起。這樣當代碼發(fā)生改變的時候,文檔也能相應更新。注釋就是一種內(nèi)部文檔,良好的注釋應該反映代碼的最新狀況。
類比來看,單元測試同樣也是內(nèi)部文檔,因為單元測試本質(zhì)上也是描述了被測類或被測方法的行為。對軟件行為不了解的程序員,可以通過閱讀單元測試 代碼來理解軟件的行為。同注釋相比,單元測試還具有一個更好的特性:它是一種“可執(zhí)行”文檔。如果單元測試在被執(zhí)行時無法通過,那么說明要么單元測試沒有 反映當前代碼的真實狀況,要么說明代碼中有bug。無論哪種情況,程序員都需要修改某一方,以保持兩者的一致。
1.4.2 具有可測試性的軟件具有更高的質(zhì)量
近年來流行的極限編程方法論推崇“測試驅(qū)動開發(fā)”。我們認為,“測試驅(qū)動開發(fā)”并不一定要求必須先有測試后有代碼,而關(guān)鍵在于要求在設計軟件和 實現(xiàn)編碼時,一定要預先把軟件的可測試性考慮周全。這種可測試性的重要體現(xiàn)就是能夠方便地將單個類或方法納入單元測試之中。具有可測試性的軟件的質(zhì)量往往 高于不具有可測試性的軟件,為什么這樣說呢?
● 一個類能夠被方便地納入單元測試,往往說明這個類職責單一,也就是說它滿足“單一職責原則”。
● 一個類能夠被方便地納入單元測試,往往說明它與其他類之間的耦合程度較低,相互依賴性較小,而且很可能滿足“依賴抽象原則”和“開放-封閉原則”。
因此, 具有可測試性的軟件,也更有可能是滿足良好設計原則的軟件,所以往往質(zhì)量更高。
1、單元測試 XXXX作為一個新項目,和其他所有項目一樣,在開發(fā)工作進行之初就在考慮如何保證代碼開發(fā)的質(zhì)量。答案很容易找到:充分的單元測試。但是以前真正做得好得項目卻不多。
經(jīng)過分析,總結(jié)了一下做好單元測試工作的四個要素:
――思想上的重視
――計劃上保證
――測試手段保證
――測試效果的可驗證
1.1 思想上重視
從以往的開發(fā)過程總結(jié)了一些教訓:
――開發(fā)人員模塊在交付聯(lián)調(diào)前,測試不充分,導致聯(lián)調(diào)周期較長
――代碼進入維護期后,修改代碼往往引起不可預期的錯誤。導致開發(fā)人員比較害怕在相對穩(wěn)定的代碼上進行修改。
由于有這些教訓,所以在編碼之前,項目從領(lǐng)導到員工都很重視單元測試工作。
1.2 計劃上保證
計劃制定經(jīng)過了XXXX的分解流程,XXXX分解小組成員討論時充分意識到了單元測試工作的必要性,也意識到了為之要付出的代價。最終確定了單元測試和代碼開發(fā)的比重為2:1左右。以操作維護的配置模塊為例,最終制定的計劃如下:

從計劃中可以看出,花在單元測試上的時間和代碼開發(fā)的時間的比重甚至超過了2:1。
附:表方法測試代碼和被測代碼的行數(shù)統(tǒng)計數(shù)據(jù)(該部分測試覆蓋率已達95%以上)
從數(shù)據(jù)看來,我們當初的估計是對的。測試代碼行數(shù)和被測代碼行數(shù)的比值大致是2:1
1.3 測試手段保證
1.3.1 測試框架
對于一個系統(tǒng)工程來說,一個好的框架是至關(guān)重要的。一個好的框架應該有如下一些特點:
――結(jié)構(gòu)清晰。不同的粒度層次在框架中一目了然
――可擴展性良好。
我們選擇單元測試工具就是為了滿足能基于該工具搭建一個好的單元測試框架,另外還有以下幾個方面的考慮:
――整個開發(fā)組負責三個模塊:數(shù)據(jù)庫,操作維護,傳輸。每個模塊由不同的開發(fā)人員負責。從測試代碼的可維護性考慮,需要統(tǒng)一各人編寫測試代碼的框架以及風格
――前臺代碼是由C語言編寫的
――單元測試應該是可回歸的,自動化的
最終我們選擇了Cunit,因為Cunit完全符合我們的要求。
Cunit是一種針對于C語言代碼的單元測試框架,具有以下優(yōu)點:
–提供很多實用的接口函數(shù)(比如:各種斷言、信息匯總、打印等)
–簡單、安全、方便
–可添加任意多個測試單元
–逐步添加,匯總成套,全面回歸測試
–完全免費
測試框架選定以后,在組內(nèi)進行了相應的培訓,使得每個人的認識達到相同的水平,盡可能保證以后測試風格的一致。
1.3.2 測試方案和測試規(guī)程
單元測試是一個系統(tǒng)的工作,在進行之前需要一個總體的規(guī)劃。這個工作在單元測試方案中得到體現(xiàn);測試規(guī)程是對測試方案的細化,是單元測試的指導。測試規(guī)程可以在notes的測試管理平臺中錄入。
1.3.3 自動化測試的考慮
一個單元測試是由許多測試用例構(gòu)成的,要實現(xiàn)可回歸,自動化的測試,每個測試用例都應該可以按照一定的順序連續(xù)的執(zhí)行。要達到這個目標,就要求對整個單元測試做一個很好的規(guī)劃。以數(shù)據(jù)庫模塊為例,我們做了如下的規(guī)劃:
● 數(shù)據(jù)環(huán)境的建立
數(shù)據(jù)庫的測試依賴于一套數(shù)據(jù)表。單元測試一開始就需要在表中插入數(shù)據(jù),以建立后續(xù)測試用例依賴的數(shù)據(jù)環(huán)境
● 測試粒度合理的劃分
我們把被測對象分為表方法、查詢接口和修改接口。

注:測試用例套(TestSuite)是Cunit的一個概念,用于封裝測試用例套或者測試用例(TestCase)
● 在某種粒度上面保證數(shù)據(jù)環(huán)境的松耦合
所有的測試用例基于數(shù)據(jù)環(huán)境編寫,數(shù)據(jù)環(huán)境對于數(shù)據(jù)庫的單元測試是及其重要的。每個測試用例從前一個用例繼承數(shù)據(jù)環(huán) 境,又將本測試用例修改后的數(shù)據(jù)環(huán)境移交給下一個測試用例。從這個過程可以看出,數(shù)據(jù)環(huán)境在測試用例間是緊耦合的。如果測試用例一多,數(shù)據(jù)環(huán)境就會變得難 以控制,用例的編寫將變得非常麻煩。尤其是多人分工合作的情況下,簡直變成了不可能的任務。
幸好我們有測試套!
以表方法的測試套為例,在第二級粒度上,一個套就是一張表所有表方法測試用例的集合。這個套包含的測試用例大致在 4、5個左右。我們的策略是:二級粒度測試套內(nèi)部的測試用例緊耦合數(shù)據(jù)環(huán)境,而二級粒度套之間的數(shù)據(jù)環(huán)境為松耦合。這通過在每個二級粒度套內(nèi)加入一個數(shù)據(jù) 環(huán)境恢復用例來實現(xiàn)。
這樣一來,我們可以按照二級粒度的測試套來分工編寫測試代碼。
● 樁函數(shù)的考慮
單元測試是不依賴其他模塊的測試。但是實際代碼中存在對其他模塊函數(shù)的調(diào)用。為了消除這種依賴關(guān)系,就需要編寫樁函數(shù)來替代對外部模塊函數(shù)的調(diào)用。
為了清晰起見,我們的樁函數(shù)集中在一個文件中。
樁函數(shù)只是一個實現(xiàn),至于原形定義,還是要引用其他模塊的頭文件,這樣可以跟蹤其他模塊函數(shù)接口最新的改動。
● 單元測試工程的規(guī)劃
一個清晰合理的單元測試工程規(guī)劃是必要的,這樣有利于對象的查找和維護。
以DBS模塊的單元測試為例,我們的工程結(jié)構(gòu)如下圖所示。
1.3.4 測試效果的可驗證
如何來量化單元測試的效果?
覆蓋率是一個最直觀的指標。我們采用了Rational公司的Pure Coverage工具來獲取覆蓋率。這也是公司推薦的一個簡單易上手的工具。我們要求測試覆蓋率達到90%以上。
2、自動測試
2.1 每日構(gòu)造
每日構(gòu)造是公司推行的一個最佳實踐。它有一下一些功能:
● 開發(fā)人員及時把最新代碼放入代碼庫,及時check out和check in,避免積累大量代碼
● 及時進行模塊間的整合,及時發(fā)現(xiàn)問題
● 對部分功能進行測試,無需等待
● 使用測試用例工具,對功能進行完整和重復的檢驗
● 記錄所有程序問題
● 實現(xiàn)解決Bug的自動流程
● 提高正式版本提交的質(zhì)量和速度
● XXXX在軟件上開始推行每日構(gòu)造。
2.2 冒煙測試
如果在每日構(gòu)造的同時進行“冒煙測試”,能最早發(fā)現(xiàn)版本的問題。“冒煙測試”是微軟提出的名詞。其
對象是每一個新編譯的需要正式測試的軟件版本,目的是確認軟件基本功能正常,可以進行后續(xù)的正式測試工作。“冒煙測試“的執(zhí)行者是版本編譯人員。
ZXWR-RNS網(wǎng)管項目已有用Robot在網(wǎng)管產(chǎn)品進行冒煙測試的實踐。但是對于前臺軟件,公司內(nèi)部尚未有這方面的嘗試。
2.3 自動測試
基于前期模塊的單元測試工作,一個自然的想法就是在每日構(gòu)造的框架上運行模塊的單元測試代碼,實現(xiàn)前臺軟件的每日自動化測試。這是比“冒煙測試”更全面,更徹底的一個測試。
2.3.1 單板軟件
模塊1
模塊2
模塊n
模擬測試工具
XXXX軟件的架構(gòu)
2.3.2 自動測試的規(guī)劃
2.3.2.1 自動測試的級別
從軟件的構(gòu)成來考慮,分三級構(gòu)造測試工程:
● 模塊單元級:基本的軟件單元
● 板級:由多模塊組成的單板軟件
● 系統(tǒng)集成級:由模擬測試軟件和單板軟件組成
這三個級別的測試是互相補充的關(guān)系。每日構(gòu)造進行三個級別的測試,這樣就比較完整了。
2.3.2.2 測試代碼的管理
要實現(xiàn)每日構(gòu)造并進行自動測試,必須將測試代碼也提升到被測代碼同等的地位,納入CC的管理。在CC上的目錄結(jié)構(gòu)如下所示:
BoardSWS
-BoardTest \\板級測試工程目錄,包含測試用例代碼以及板級測試工程
-SystemTest \\系統(tǒng)集成級工程目錄,不含測試用例,僅含工程
-SCMM
-UnitTest \\模塊單元級測試目錄,包含該級測試用例以及測試工程
-SCMM源碼
-Cunit \\模塊單元級測試用Cunit文件
-其他模塊
-Cunit \\板級測試用Cunit文件
SimuRNC
-SystemTest \\系統(tǒng)集成級測試工程目錄,包含測試工程和測試用例
-SimuRNC源碼
-Cunit
基于這樣的目錄結(jié)構(gòu),發(fā)布代碼和測試代碼在CC中納入同一個VOB管理比較方便。這樣一來,測試代碼和發(fā)布代碼執(zhí)行相同的CC-CQ關(guān)聯(lián)策略。也就是說,修改測試代碼也需要和CQ關(guān)聯(lián)。
2.3.3 自動測試的執(zhí)行
自動測試由版本室完成。自動測試執(zhí)行分為四個步驟:
● 同步CC的文件到本地
● 編譯各測試工程(VC環(huán)境),輸出編譯結(jié)果文件
● 執(zhí)行編譯結(jié)果,輸出測試執(zhí)行結(jié)果文件
● 將編譯結(jié)果導入
notes
上述步驟都是通過編寫一個BAT文件掛在WINDOWS的“任務計劃”中每日自動完成。
附:
BAT文件(DBS模塊)
編譯結(jié)果 (DBS模塊)
測試執(zhí)行結(jié)果(DBS模塊)
2.3.4 自動測試的目標
依附于每日構(gòu)造機制,自動依序運行三個級別的自動測試,測試產(chǎn)生的結(jié)果輸出到文本文件。然后通過自動分析腳本提取該文件中沒有通過的用例信息,發(fā)送郵件通知相關(guān)責任人進行分析處理。如此做到“日事日畢”,將故障扼殺在萌芽狀態(tài),保證代碼質(zhì)量的穩(wěn)定。
單元測試的效益
單元測試是針對代碼單元,特別是算法密集的代碼單元的獨立測試,可以完整覆蓋代碼單元的功能邏輯,保證代碼質(zhì)量、降低成本、提高生產(chǎn)率、縮短開發(fā)周期、贏得市場先機、提升產(chǎn)品競爭力。
單元測試分為靜態(tài)和動態(tài),靜態(tài)方法只能發(fā)現(xiàn)小部分錯誤,例如,加法函數(shù)
int add(int a, int b){return a-b;};
加號寫成了減號,這種最簡單代碼中的最簡單錯誤,任何靜態(tài)工具都無法發(fā)現(xiàn),而動態(tài)方法只需輸入兩個1,自動判斷輸出是否等于2,馬上就能發(fā)現(xiàn)錯誤。靜態(tài) 方法能發(fā)現(xiàn)的錯誤,如除零錯、數(shù)組越界、條件語句中==寫成=,都會表現(xiàn)為異常或功能錯誤,動態(tài)方法當然也能發(fā)現(xiàn),因此,動態(tài)方法是單元測試的根本方法。
無處不在的80-20規(guī)則,在軟件開發(fā)中 同樣存在,例如,80%的錯誤存在于20%的代碼中,80%的項目時間消耗在20%的代碼上,當然這只是粗略的估計。“20%代碼”就是邏輯復雜的代碼, 也就是算法密集的代碼。一個算法密集的函數(shù),要對輸入仔細分類,一個判定就是一次分類,嵌套的判定更使分類次數(shù)翻番,遺漏一個分類,或一個分類處理不正 確,就會造成錯誤。只有完整覆蓋代碼單元的所有輸入等價類,才能保證發(fā)現(xiàn)這些錯誤,這在調(diào)試和系統(tǒng)測試中是難于做到的。算法密集的代碼包含了項目中的大多數(shù)錯誤,即使只對這部分代碼實施單元測試,也能產(chǎn)生理想的效益。
除了保證代碼質(zhì)量,單元測試還具有排錯成本最低、易于自動回歸、縮短后續(xù)測試周期、提高編程效率等顯著效益。