2009年2月14日 星期六

[Software Testing] About software testing

第一次有老師在中央資工開了軟體測試這門課,如果對於軟體測試這領域有接觸過的話,相信你對於劉龍龍教授並不陌生,台灣應該就屬他是首席吧~以下是我修完這堂課後所寫的報告,內容如果有問題歡迎大家留言提出喔~

軟體測試的中心原則就是:證明程式有錯誤。所以我們要假定該程式有一個錯誤E,並且給予一組輸入值為i,期望的輸出值為o,當我們將i輸入給程式後所得到的實際輸出為o’,若o與o’不相等,我們就可以大聲的說,這程式有E這樣的錯誤存在;否則,我們就不表示任何意見。不表示意見不代表程式沒有E這樣的錯誤,而是我們的輸入值並不會讓程式產生E這樣的錯誤。

軟體開發的生命週期中,最有名也是最基本的方法是waterfall,也就是將軟體的開發分為需求、分析、設計、開發、測試與維護。在軟體測試中也有一個V-Model,他是一個waterfall model的一種延伸,在V-Model中,軟體生命週期中分為需求分析、系統設計、架構設計、模組設計與撰寫程式,並且將各個階段加入對應的測試階段,分別為驗收測試、系統測試、整合測試與單元測試。當我們在需求分析階段時,我們就要針對需求文件來設計驗收測試文件;當我們在系統設計時,我們就要撰寫出系統測試文件,並且以此類推。當我們撰寫完成我們的程式後,我們就要依序執行單元測試、整合測試、系統測試與驗收測試。也就是測試文件是與軟體的生命週期平行的開發,而測試動作則是反向來執行。

軟體測試的最小單元就是單元測試,而單元測試又可以根據輸入資料的Boundary Value進行測試,因為每個function都會有input data range,也就是有max與min的值,我們可以測試{min-1,min,min+1,max-1,max,max+1}的集合來測試function對於邊界值是否會產生錯誤。接下來我們可以將input data採用equivalence class testing,將input data作分類,將結果類似的區分成同一個class,藉由這樣的方法來找出屬於equivalence class中的值來進行Normal與Robust的equivalence class testing。我們可以在透過equivalence class進行decision table testing,decision table可以讓我們將複雜的邏輯結構化,並且可以在設計test case時作為參考的依據,所以透過decision table讓我們可以檢查出equivalence class切割時的缺陷。這些測試的方法主要是black box testing,因為我們不考慮程式內部的邏輯,只是針對input data來進行測試;相反地,white box testing就是特別針對程式內部的邏輯進行測試,所以我們可以對程式可能執行的path進行測試,這種就是path testing,藉由path的trace,我們可以找出程式中的branch或loop的coverage,也可以觀察出程式的複雜性。有了path testing,我們要在詳細的根據dataflow進行測試,觀察某些變數在某些path中的變化,對於最簡單的測試就是在程式碼中加入列印出某些變數的目前狀況,進而找出程式中data在經過複雜變化後所產生的bug。

藉由上述的unit testing,我們不僅針對功能本身的black box testing,也針對了程式內部進行white box testing。Unit是程式中最小的單元,若每一個unit都完成了測試,系統中的各個unit就必須進行整合,而針對整合的測試就是integration testing。在Integration testing因為我們不可能將所有的unit同時都進行整合,所以,測試整合的tree中,我們將被unit呼叫的假程式稱為stub,而呼叫unit的假程式稱為driver,整合主要有三種測試的方式,第一是Decomposition-based integration,由source code來得到系統功能之間的decomposition tree,然後由某一個source開始進行top-down或bottom up的測試,也可以結合top-down與bottom-up兩種的sandwich等。第二種則是Call graph-based integration,從tree中進行測試,pair-wise integration是一次針對一對的unit進行測試,這樣的測試可以使用到最少的stubs/drivers,不過缺點是整合的次數較多,當系統中的unit數量很多時,兩兩測試的次數就會增加,而另一種Call graph-based integration是neighborhood integration,根據某個source將所有的鄰居都加入測試,這種方式可以不需要任何的stub/driver。第三種的整合測試方式為Path-based integration,根據program tree進行整合測試。當整合測試中有發現任何unit之間存在問題,就必須修改,修改完成後就要進行回歸測試(Regression testing),回歸測試就是將被修改的unit與他相關的units進行一次整合測試,然後系統在整體的進行整合測試,這樣才能保證被修改的unit不會影響到相關的units與系統中的所有units。

整合測試完成後,最後就是系統測試,所謂的系統就是只軟體與硬體,所以系統測試就是將我們所開發的軟體佈署到硬體上進行測試,因為軟體在開發機器上可以執行,不代表在正式機氣上是可以執行的,所以要進行軟硬體的系統測試。確定軟體可以在硬體上正確的執行後,我們就要將系統交付給客戶,因為客戶是最終測試者,也就是要進行驗收測試,在驗收測試中,客戶需要針對需求中的內容一一進行驗收。

以上就是根據V-Model中測試的生命週期,不過隨著程式語言的方法演進,傳統的Structure Programming Language上的測試方法對於新的Object-oriented programming language就不見得完全有用,所以針對OO的測試方式有些方面是需要修改的。傳統的程式對於unit的定義在於一個最小的可執行component,或是由同一個開發者所撰寫的component。對於OO來說,unit則是Class,這樣的優點是對於test case的定義可以更加的明確,以使得整合測試時目標更明確(Classes 之間的整合),不過OO測試所存在的缺點為大多數的測試工作都轉移到整合測試上,原先傳統的整合測試是針對components之間的測試,而OO中可能有很多的Classes才會組成一個component,也就是OO中的unit更小了。另外,OO中提供了inheritance的特性,繼承讓程式可以很輕易的reuse,不過對於測試來說也就更加的棘手,所以針對繼承結構中的subclass,我們可以採用Flattened class方式,將該subclass所繼承的所有super classes的fields與methods都寫入該subclass中,也就是將class壓扁平,這一樣就可以只針對此subclass進行測試。對於OO來說,測試的階層會因為unit的定義不同而有不一樣的階層結構:若是以class為unit,則就會與傳統的測試階層類似;若是以method為unit,則測試階層會在多了一個level,最下層就是unit testing,但是因為methods是存在class中,所以在unit testing與integration testing之間就會多了class testing。

根據測試的生命週期所發展的測試方法有很多種。Exploratory Testing是一種測試的方式,藉由測試者本身對於問題的瞭解(也就是domain knowledge)、對於程式的技巧與經驗來進行測試,這種測試方法有點像是口試一樣,測試者是口試官,而被測試的程式則是面試者,測試者根據本身的經驗來設計可能的bug存在的test case並測試該程式。這種測試方式對於測試者本身的要求很高,因為測試者需要對於domain有一定的知識與經驗,否則無法從很刁鑽的角度找到程式的問題,而且測試者需要對於程式抱持著好奇心與創造力,去思考一些程式中沒有考慮到的問題。不過對於較複雜的系統就不太適用,因為系統若很龐大,測試者很難去全面的考慮,所以針對小的系統來說,這種測試方式比較有效。

Model-based Testing是一種針對系統中的Model進行測試。複雜的系統通常很難測試,如果測試系統的model就會讓問題簡化,以利於測試的執行!首先將系統進行modeling,把複雜的部份去除而不失真,接著就是找出一條系統的執行過程但是不考慮太多的例外情況,並將這樣的執行過程撰寫成test case在進行測試。

在Extreme programming(XP)中提出的Test-driven Development(TDD)是一種很不一樣的測試方式,要求programmer先根據scenario撰寫出test case,有了test code在撰寫source code。這樣的測試方式打破了傳統的思維:先有test code再有source code。不過這樣的測試方法有一個盲點,programmer針對test case來寫source code,這樣的source code並不保證在test case修改後還能夠執行,也就是source code會陷於滿足test case的盲點。不過,我們實驗室是提倡agile method,所以在計畫的開發上也有採用TDD,就我自己本身對於TDD的看法,我覺得source code會陷於這樣的盲點會是當初客戶給的scenario本身就存在盲點,若是scenario在描述使用者的動作有全面的考慮到,test cases就不會只有侷限在某些情況下,當然source code也就不會受到侷限。在TDD的使用上,我個人覺得很不一樣,以往都是先針對scenario去思考系統的層面,藉由TDD的先撰寫test cases,讓programmer可以先根據使用者的角度去思考我要怎樣用系統,這樣對於scenario的內容就必須更深入的思考,而且當source code完成後,programmer往往會懶得去撰寫test code,測試上就會有盲點,而測試的方式都是以執行看看在找問題,這種方式讓programmer在測試的角度就不太客觀,所以TDD將programmer的思考角度反轉,先知道使用者真正的使用角度再去撰寫程式。

2 則留言:

David Ko (柯仁傑) 提到...

很難得看到有人對testing有興趣換營一起交流討論
http://www.wretch.cc/blog/kojenchieh

Maverick 提到...

感謝分享!!這篇很實用!