測試驅動開發(TDD)是當前流行的開發理念,XCTest是Apple封裝的單元測試類庫。使用XCTest進行單元測試的流程比較簡單,本博客僅簡單介紹下XCTest的使用。但對於單元測試的理解(何時使用,如何更高效地驅動開發)卻是一個需要積累的過程,要在TDD的過程中仔細體會。
一個測試用例以一個Objective-C類的形式存在,其implementation中有默認的setUp和tearDown方法,分別用於處理用例執行前的准備工作和執行完畢後的清理工作。
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
因此,常在setUp中進行基本對象的初始化等一些必需的步驟。
所有以test開頭的實例方法(不能有參數)都是一個完整的單元測試的case。
最左邊有個小圖標,點擊之後即可執行該測試case。
性能測試可直接使用 - (void)measureBlock:(void (^)(void))block; 進行,傳遞參數即為要執行任務組成的一個block。
如以下的case可用於測試measureBlock接收的block參數中執行的性能。
Baseline即可設置該性能是否合格的參考基准。
注意,一個性能測試case中只能執行一次measureBlock,即只能測試一個指定block步驟的性能(這其實也是合乎情理的,混到一塊了哪裡還能區分出性能測試的結果)。因此,在while,for等循環中是不能使用measureBlock的,否則會出錯。
measureMetrics的用法跟measureBlock類似。
當然,也可以使用startMeasuring和stopMeasuring方法進行性能測試,但絕大多數情況下使用measureBlock已經足夠了。
例如,在單獨測試一個圖片濾鏡的性能時,可以這樣:
- (void)testPerformanceFilter1 {
[self measureBlock:^{
NSInteger filterid = 100;
UIImage *filteredImage = [FilterTool filterImage:self.image withFilterId:filterid];
XCTAssertNotNil(filteredImage, @"濾鏡效果圖應該非空");
}];
}
那麼,如果有幾十個濾鏡需要進行性能測試呢?
因為單元測試case的方法名不能傳遞參數,且循環中不能使用measureBlock,因此對於大量重復的性能測試case,目前看來采用宏定義是比較好的解決方法。
#define TestFilterPerformance(filterid) - (void)testFilterPerformance_##filterid { \
[self measureBlock:^{ \
UIImage *filteredImage = [FilterTool filterImage:self.image withFilterId:filterid]; \
XCTAssertNotNil(filteredImage, @"濾鏡效果圖應該非空"); \
}]; \
}
則使用起來,就可如下定義所有的測試case了。
XCTest提供了XCTestExpectation來進行異步測試,即可在指定時機使用其fulfill實例方法開始執行斷言命令。例如以下是一個完整的網絡請求的測試樣例,可用於測試server端是否正常,要在對應的block中進行斷言。
- (void)testAsynchronousURLConnection {
XCTestExpectation *expectation = [self expectationWithDescription:@"GET Baidu"];
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com/"];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// XCTestExpectation條件已滿足,接下來的測試可以執行了。
[expectation fulfill];
XCTAssertNotNil(data, @"返回數據不應非nil");
XCTAssertNil(error, @"error應該為nil");
if (nil != response) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
XCTAssertEqual(httpResponse.statusCode, 200, @"HTTPResponse的狀態碼應該是200");
XCTAssertEqual(httpResponse.URL.absoluteString, url.absoluteString, @"HTTPResponse的URL應該與請求的URL一致");
} else {
XCTFail(@"返回內容不是NSHTTPURLResponse類型");
}
}];
[task resume];
// 超時後執行
[self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) {
[task cancel];
}];
}
即:異步網絡請求操作正常執行,但斷言命令僅在[expectation fulfill];結束後才開始執行,這樣能確保得到網絡請求的返回結果後執行相應斷言。同時,可設置超時時間及對應操作。
否則,就需要使用定時機制等處理類似情況。
在case中,如何訪問一個待測類的私有變量呢?對該類進行相應的擴展即可。
#import
#import "FilterTool.h"
@interface FilterTool (UnitTest)
@property (strong, nonatomic) FilterController *filterController;
- (void)didSelectFilterCellAtIndexPath:(NSIndexPath *)indexPath;
@end
@interface FilterToolTest : XCTestCase
@end
@implementation FilterToolTest
XXXX
@end
FilterTool類的filterController屬性和didSelectFilterCellAtIndexPath:方法本是私有的,若想對其進行測試,則必須將其加入擴展。
XCTFail(format…) 生成一個失敗的測試;
XCTAssertNil(a1, format...)為空判斷,a1為空時通過,反之不通過;
XCTAssertNotNil(a1, format…)不為空判斷,a1不為空時通過,反之不通過;
XCTAssert(expression, format...)當expression求值為TRUE時通過;
XCTAssertTrue(expression, format...)當expression求值為TRUE時通過;
XCTAssertFalse(expression, format...)當expression求值為False時通過;
XCTAssertEqualObjects(a1, a2, format...)判斷相等,[a1 isEqual:a2]值為TRUE時通過,其中一個不為空時,不通過;
XCTAssertNotEqualObjects(a1, a2, format...)判斷不等,[a1 isEqual:a2]值為False時通過,
XCTAssertEqual(a1, a2, format...)判斷相等(當a1和a2是 C語言標量、結構體或聯合體時使用,實際測試發現NSString也可以);
XCTAssertNotEqual(a1, a2, format...)判斷不等(當a1和a2是 C語言標量、結構體或聯合體時使用);
XCTAssertEqualWithAccuracy(a1, a2, accuracy, format...)判斷相等,(double或float類型)提供一個誤差范圍,當在誤差范圍(+/-accuracy)以內相等時通過測試;
XCTAssertNotEqualWithAccuracy(a1, a2, accuracy, format...) 判斷不等,(double或float類型)提供一個誤差范圍,當在誤差范圍以內不等時通過測試;
XCTAssertThrows(expression, format...)異常測試,當expression發生異常時通過;反之不通過;(很變態)
XCTAssertThrowsSpecific(expression, specificException, format...) 異常測試,當expression發生specificException異常時通過;反之發生其他異常或不發生異常均不通過;
XCTAssertThrowsSpecificNamed(expression, specificException, exception_name, format...)異常測試,當expression發生具體異常、具體異常名稱的異常時通過測試,反之不通過;
XCTAssertNoThrow(expression, format…)異常測試,當expression沒有發生異常時通過測試;
XCTAssertNoThrowSpecific(expression, specificException, format...)異常測試,當expression沒有發生具體異常、具體異常名稱的異常時通過測試,反之不通過;
XCTAssertNoThrowSpecificNamed(expression, specificException, exception_name, format...)異常測試,當expression沒有發生具體異常、具體異常名稱的異常時通過測試,反之不通過.