單元測試 Unit Test,簡單來說就是使用程式來測試程式並且把「測試」這種工作變得更自動化,在大型專案上單元測試更是扮演不可或缺的角色,除了可以提昇開發速度之外,相對的也可以減少因為 Bug 或是邏輯錯誤等所造成的損失。
近年來由於敏捷式開發的興起,單元測試也受到相對的重視,能夠將測試自動化並且測試範圍能夠和蓋所有程式碼等這街都是在撰寫單元測試時的重點,而單元測試也有所謂類似 SOP 的標準作業流程 TDD - Test Driven Developer (Design),顧名思義在開始撰寫程式時,必須先寫他的單元測試,之後才開始真正撰寫程式碼,大致上的流程是:寫測試 → 讓測試通過 → 重構 → 再進寫下一步的測試,並且一次只能出現一個測試失敗的 case,使用 TDD 這種方式來開專案,幾乎可以測試到所有的程式碼,而且在越複雜的專案裡越能顯現出它的成效。
在開始做單元測試之前,先來看待測的程式碼,其程式碼如下。
Rectangle.h
#import <Foundation/Foundation.h>
@interface Rectangle : NSObject {
int height;
int weight;
int area;
}
@property int height, weight, area;
-(Rectangle *)initWithHeight:(int)h andWeight:(int)w;
@end
Rectangle.m
#import "Rectangle.h"
@implementation Rectangle
@synthesize height, weight, area;
-(Rectangle *)initWithHeight:(int)h andWeight:(int)w {
self = [super init];
if (self) {
height = h;
weight = w;
area = h * w;
}
return self;
}
@end
上述程式碼演示了一個簡單的例子,在我們建立這個 Rectangle 物件的同時,可以給予它長與寬,並且它會自動換算出面積並儲存於 area 變數中。
看完代測程式碼之後,現在讓我們來看看如何設置單元測試所需要的環境。
首先先在專案中建立一個 Unit Test Bundle 的新 Target ,方法是在專案上按下滑鼠右鍵 > Add > New Target,之後選擇 Unit Test Bundle,接下來在 Target Name 中為這整個測試取名。
接著把 Active Target 設定成剛剛的 Bundle(UnitTest),並使用 Simulator 執行。
到這裡為止,已經完成了單元測試所需要的環境,下面就來看看如何開始寫單元測試的內容。
首先要建立 Test Case Class,建立的方法是在專案上按下滑鼠右鍵 > Add > New File > Objective-C Test Case Class,之後在 Targets 的地方勾上剛剛的 Bundle(UnitTest),而在命名的部份,由於一個 Test Case Class,就只對單一個待測的 Class,因此名稱部份就直接使用待測的 Class 加上 Test方便辨識。(命名的部份並沒有硬性規定,如果是要作整合測試,意指兩個以上不同的 Class 彼此之間有一定的耦合程度,必須放在一起作測試,可以另外再使用一個 Class 專門測試這幾個彼此相依的 Class,測試時就對該 Class 測試)
由於是使用樣板建立 Test Case Class 的關係,在 RectangleTest.h 和 RectangleTest.m 中充斥著與多原生程式碼,目前我們並不需要用到它,因此這邊建議先全部註解或是刪除,在刪除之後,兩個檔案應該如下。
RectangleTest.h
#import <SenTestingKit/SenTestingKit.h>
#import <UIKit/UIKit.h>
@interface RectangleTest : SenTestCase {
}
@end
RectangleTest.m
#import "RectangleTest.h"
@implementation RectangleTest
@end
接下來是個重點,由於我們先前所建立的 Bundle(UnitTest)並不認識 Rectangle.m(因為它早在我們建立 Bundle 就存在於專案中),所以請千萬記得,務必要幫它們做連結,連結的方式是在 Rectangle.m 上按下滑鼠右鍵 > Get Info > Targets >把 Bundle(UnitTest)也打勾。另一種方式則是直接拖曳 Rectangle.m 到 Bundle(UnitTest)內的 Compile Sources 下。
緊接著我們在 RectangleTest.h 中 #import 待測試的 Rectangle.h 並宣告一個物件與測試的函式。
緊接著我們在 RectangleTest.h 中 #import 待測試的 Rectangle.h 並宣告一個物件與測試的函式。
RectangleTest.h
#import <SenTestingKit/SenTestingKit.h>
#import <UIKit/UIKit.h>
#import "Rectangle.h"
@interface RectangleTest : SenTestCase {
Rectangle *theRect;
}
- (void)testArea;
@end
在 RectangleTest.m 中除了實做上述的測試函式,這裡也多加了兩個針對測試時的資源管理 setUp 與 tearDown 兩個 Methods,程式會自動在測試一開始與結束時分別執行這兩個 Method。
RectangleTest.m
#import "RectangleTest.h"
@implementation RectangleTest
//setUp函式寫入測試之後的環境初始化
- (void)setUp {
theRect = [[[Rectangle alloc]initWithHeight:8 andWeight:25]retain];
//建立失敗回傳Could not create test subject.
STAssertNotNil(theRect, @"Could not create test subject.");
}
//tearDown函式寫入測試之後的物件釋放
- (void)tearDown {
[theRect release];
}
//測試的功能前面加入test字樣
- (void)testArea {
int expected = 200;
int result = theRect.area;
//測試兩數值必須相等
STAssertEquals(expected, result,@"We expect %d, but it was %d", expected, result);
}
@end
要如何知道測試是否通過,在這裡我們故意修改 expected 為錯誤的答案,讓其與計算之後的結果比對產生錯誤,如果您是按照 TDD 的方式來實作,那麼同一時間只會出現一個錯誤,測試錯誤的狀態有點類似編譯失敗,會在 Xcode 編譯器的右下角寫出 Failed 的字樣與錯誤的個數,點開之後就可以查閱錯誤的地方,大致上如下(點圖可放大)。
STAssert 能判斷的東西當然不只上述這些,有興趣的讀者可以自行實驗。
再次強調以上述這個這麼小的範例是看不出單元測試所帶來的好處,但是如果是在更大的專案上你一定可以從單元測試中得到不少助益。
沒有留言:
張貼留言