由於人們對於軟件質量的重視程度越來越高,就導致瞭測試在軟件開發中的地位越來越重要。測試是目前用來驗證軟件是否能夠完成所期望的功能的唯一有效的方法。在這一趨勢的引導下,現在很多軟件相關的公司都非常重視對於他們所開發的軟件的測試,甚至不惜花費巨資購買商用的測試工具,但是效果卻不一定理想。究其原因主要是存在著對於軟件測試的諸多誤解。本文試圖對一些比較普遍的關於測試的誤解進行剖析,並且在測試對於軟件產品質量可能帶來的更深遠的影響方面,也進行瞭論述。
測試在軟件開發過程中一直都是備受關註的,即使在傳統的軟件工程中,也有一個明確、獨立的測試階段。隨著軟件危機的頻頻出現以及人們對於軟件本質的進一步認識,測試的地位得到瞭前所未有的提高。測試已經不僅僅局限於軟件開發中的一個階段,它已經開始貫穿於整個軟件開發過程,人們已經開始認識到:測試開始的時間越早,測試執行的越頻繁,所帶來的整個軟件開發成本的下降就會越多。Extreme Programming更是把測試推到瞭極限的位置,一切軟件開發活動都要從首先編寫測試代碼開始。
但是,相對於測試這個詞的流行程度而言,有關測試教育方面的工作做的還遠遠不夠,很多關於測試的文章都是針對某種測試工具使用方面的,而測試工具廠商也往往出於商業的目的對於測試工具的作用誇大其詞。這樣,很多的軟件從業者就很容易陷入一些誤區,導致瞭測試並沒有在他們所在的軟件開發項目中起到有效的作用。下面幾個小節將對於一些比較具有代表性的誤區進行剖析,並對於測試背後所蘊含的一些設計思考進行瞭闡述,希望能夠起到拋磚引玉的作用。
誤區之一:使用瞭測試工具,就是進行瞭有效的測試
這個誤區可以說是一種通病,幾乎每一個領域中的CASE工具剛剛出現時都會帶來這個問題,比如:如果一個軟件開發團隊在軟件的開發中使用瞭Rational Rose工具來進行UML圖的繪制,他們可能就會聲稱他們采用瞭面向對象的方法,也不管他們的設計和代碼實際上是多麼的過程化。
在測試領域中也一樣如此,一個軟件開發團隊往往認為隻要自己使用瞭某種軟件測試工具,那麼就應該可以獲取測試帶來的種種好處,這種想法當然是錯誤的。因為,要想對一個軟件或者模塊進行有效的測試,首先該軟件或者模塊應該是可測試的。可測試性是反映軟件質量的一個內在屬性,不會因為你使用瞭某種測試工具進行瞭測試行為,就使得被測試的軟件具有瞭可測試性。如果被測試的軟件本身並不具備可測試性,那麼使用多麼昂貴的測試工具進行測試所能夠帶來的收益都是微乎其微的。
巧的是,可測試性和好的軟件品質的其他方面有天然的關聯,一個具有可測試性的軟件必然是一個強內聚、弱耦合、接口明確、意圖明晰的軟件,而不可測試的軟件往往具有過強的耦合和混亂的邏輯。關於可測試性方面更多的內容不在本文的論述范圍,請自行參見相關的文獻(本文所附的參考文獻中有關於可測試性的更深入的信息)。
要想真正獲取測試帶來的巨大好處,並且使得測試工具能夠發揮最大的效率,關鍵就是要使軟件本身具有很好的可測試性。這種能力的獲取是一個逐步的過程,是不可能一蹴而就的。最關鍵的一點就是要不斷實踐,不斷學習一些優秀的經驗,不斷的反思。要想獲取好的結果,就必須要付出努力,這是亙古不變的真理。Extreme Programming中的測試先行的實踐倒是一個很好的起點,具體可以參見參考文獻[3]。
對於測試工具的選擇,隻要滿足需要並能夠自動運行測試用例就可以瞭。不要一味的追求復雜的功能和不必要的靈活性。對於大多數項目來說,一些非常著名的源碼開放的測試工具就足夠瞭,比如:Java方面的單元測試工具JUnit和C++方面的單元測試工具CppUnit。關於驗收測試方面,目前沒有比較好的滿足各方面需要通用的測試工具,不過使用腳本語言,循序漸進的自行開發一個適合自己的驗收測試工具也不是一件困難的事情,一句話:隻有提高瞭自身團隊內在的素質,外在的工具才能夠發揮作用。
誤區之二:存在太多的無法測試的東西
在軟件開發領域,確實存在一些東西看起來要比另外一些東西難測試一些,但是遠非無法測試。並且在大多數的情況下,還是由於被測試的軟件本身在設計時沒有考慮到可測試性的問題。隻不過這種不可測試性不是由於被測試的軟件內部的過緊耦合造成的,而是和外部某些很難測試的部分耦合過緊,從而表現出被測試的軟件本身很難測試。這些很難測試的部分比較常見的有:圖形界面、硬件、數據庫等。下面以圖形界面為例進行說明。
如果一個軟件模塊必須要通過圖形界面才能夠觸發它的應用邏輯時,那麼要對這個軟件模塊進行測試時就必須要使用圖形界面。但是圖形界面又是很難測試的。看起來好像很難辦。讓我們換一個角度考慮一下,其實我們真正想要測試的是軟件模塊本身的應用邏輯,而不是圖形界面的觸發邏輯。如果我們在設計時能夠把被測試的軟件模塊本身進行很好的封裝,定義良好的服務提供接口,那麼我們就完全可以通過軟件模塊本身提供的接口進行測試。這樣就可以繞過難以測試的圖形界面。造成上述軟件模塊很難測試的原因正是由於在設計時把軟件模塊本身的應用邏輯和圖形界面這兩個無關的邏輯耦合在瞭一起。把這兩個邏輯分離,不僅可以對該軟件模塊進行更容易、更有效的測試,而且也使得該軟件模塊具有瞭很好的可重用性和可移植性。
那麼對於不好測試的圖形界面,我們該怎麼辦呢?原則很簡單,如果某種東西不好測試,那麼就讓它做肯定不會出錯的事情,而把可能會出錯的邏輯剝離出來,放到一個可以測試的模塊中。對於圖形界面來說,就是僅僅保持一個很薄的圖形界面邏輯,它的工作就是把用戶的請求簡單的轉發給真正處理該請求的軟件模塊(一般稱之為Application Facade)。轉發邏輯足夠簡單以至於它肯定不會出錯,所以我們也無需對它進行測試。關於這方面更多的信息,請參見參考文獻[5]。
如果在進行軟件開發時能夠首先編寫測試代碼,那麼就會迫使你從易使用性,易測試性的角度開考慮問題,從而你就會專註於軟件模塊的高層抽象和職責。這樣就會定義出清晰的、明確反映意圖的模塊接口來,另外,為瞭使得測試能夠進行,你就會主動把那些導致不好測試的耦合去掉。這樣的結果不僅僅是獲得瞭可測試性,並且也產生瞭更好的設計和系統架構。
?
但是確實存在一些不好測試的東西並且很難隻讓它執行一些非常簡單的邏輯,比如嵌入式系統中的BSP(板級支持包)。開發它們所使用的語言、環境以及它們本身的特性等決定瞭它們是很難測試的。這裡說的難測試其實是一個測試代價問題,具體的說就是要付出更多的時間和努力。這就需要你在付出的代價和測試帶來的收益之間進行平衡。如果付出的代價所帶來的收益(更少的調試、維護、發佈成本)是巨大的,那麼付出的代價就是非常值得的。
誤區之三:測試代碼可以隨意寫
大傢肯定知道測試代碼是不能隨意編寫的,並且在編寫測試代碼時也不是抱著一種隨意的態度,但是你編寫出來的測試代碼以及測試代碼運行的情況卻表現出瞭一種隨意性和無序性。因為你可能並沒有弄清楚測試的真正意圖所在。
本人曾經看到過有關驗收測試的這樣一個案例,驗收測試者使用昂貴的商用測試工具對一個具有圖形界面的軟件進行測試。測試的方法是通過編寫測試腳本驅動鼠標在圖形界面上隨機的點擊(當然每一次的點擊,都點到瞭圖形界面上可以接收事件的區域),然後等待著被測試軟件的崩潰。當然這種測試方法可以作為驗收測試的一個方面,但是決不是唯一的一個方面。還有更重要的內容被忽視瞭。
測試的目的是用來檢驗軟件系統是否滿足瞭需求。所以,你的測試代碼一定要明確的表達出這一點來。就那上面的案例來說,如果測試者真正從用戶的需求出發,那麼他寫出來的測試腳本肯定不會是那樣的,而應該是每一個測試用例都清晰的刻畫瞭一項用戶的需求,然後檢驗系統是否實現瞭用戶期望的功能。這樣的測試才是有明確目的,才是最有效的測試。和把界面邏輯和應用邏輯隔離,采用明確表現用戶需求測試用例進行測試相比,上面的測試方法不能不說是隨意瞭一點。
在針對類進行的單元測試中,也經常會看到一些錯誤的測試方法。很多的測試者往往針對類中的每個特定的實現細節進行測試。類中的特定的實現細節是很容易變化的,在快速的迭代式開發中更是如此。一旦你測試的類中的某個實現細節發生瞭變化,你原先的測試代碼就要進行相應的更改,否則就失去瞭意義。這種頻繁更改的代價是巨大的。類是一種抽象,它反映瞭更高層面的內聚性,它應該有明確的職責和定義良好的服務接口,它的職責和對外的接口相對於內部的實現細節來說要穩定的多,並且我們要驗證的正是這個類是否具備瞭它的職責。因此,在對類進測試時,我們應該針對類的接口來驗證類是否實現瞭它的職責而不是其他。這樣寫出來的測試代碼要穩定的多,也有效的多。
細想一下,造成容易陷入針對實現細節測試的原因主要是由於先實現瞭類,然後才去進行測試。如果先實現瞭類的功能,然後在去對類進行測試,潛意識中就會不由自主的想去驗證已經完成的類的某種實現細節。如果能夠在編寫實際類前,首先編寫針對該類的測試代碼,情況就會有很大的不同,因為這會迫使你從類的使用者的角度去考慮問題。結果就是會把關註點放在類的易用性上,放在類的職責上面,放在類提供服務的接口上面,而不是某種實現細節。
總之,測試代碼的編寫應該從被測試的對象是否滿足需要的層面進行,而不是其他。
誤區之四:單元測試和驗收測試沒有什麼區別
和誤解之三一樣,可能很多人並不承認這一點。但是他們卻又不能比較清楚的說出二者的差別來。這樣,在他們進行測試代碼的編寫時就會比較迷茫。本小節結試圖給出一些區分