簡單使用單元測試
對方法引用AFN框架的單元測試
不寫單元測試的程序員是不合格的,為了讓自己成為一名合格的程序員,學習如何寫單元測試是很有必要的,這里以Xcode集成的測試框架XCTest為例。
XCTest基礎用法
默認的測試類繼承自XCTestCase,當然也可以自定義測試類,添加一些公共的輔助方法,所有的測試方法都必須以test開頭,且不能有參數,不然不會識別為測試方法。
@interface__Tests:XCTestCase@end@implementation__Tests// 在每一個測試用例開始前調用,用來初始化相關數據- (void)setUp {? ? [supersetUp];// 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.[supertearDown];}// 測試方法- (void)testExample {// This is an example of a functional test case.// Use XCTAssert and related functions to verify your tests produce the correct results.}// 性能測試方法,通過測試block中方法執行的時間,比對設定的標準值和偏差覺得是否可以通過測試- (void)testPerformanceExample {// This is an example of a performance test case.[selfmeasureBlock:^{// Put the code you want to measure the time of here.}];}/// 我自定義的 針對 Person 類的測試方法- (void)testPerson{}@end
斷言
XCTest的斷言具體可查閱XCTestAssertions.h文件,這里還是做個簡單的總結
//通用斷言XCTAssert(expression,format...)//常用斷言:XCTAssertTrue(expression,format...)XCTAssertFalse(expression,format...)XCTAssertEqual(expression1, expression2,format...)XCTAssertNotEqual(expression1, expression2,format...)XCTAssertEqualWithAccuracy(expression1, expression2, accuracy,format...)XCTAssertNotEqualWithAccuracy(expression1, expression2, accuracy,format...)XCTAssertNil(expression,format...)XCTAssertNotNil(expression,format...)
舉個例子
- (void)testExample {//設置變量和設置預期值NSUIntegera =10;NSUIntegerb =15;NSUIntegerexpected =24;//執行方法得到實際值NSUIntegeractual = [selfadd:a b:b];//斷言判定實際值和預期是否符合XCTAssertEqual(expected, actual,@"add方法錯誤!");}-(NSUInteger)add:(NSUInteger)a b:(NSUInteger)b{returna+b;}
從這也能看出一個測試用例比較規范的寫法,1:定義變量和預期,2:執行方法得到實際值,3:斷言
當然在有些特殊情況下直接使用這些斷言,會讓代碼看起來很臃腫,使用時可以對其進行一定的封裝一下:
#define ml_ssertTrue(expr)XCTAssertTrue((expr), @"設置指定的字符串,同下")#define ml_assertFalse(expr)XCTAssertFalse((expr), @"")#define ml_assertNil(a1)XCTAssertNil((a1), @"")#define ml_assertNotNil(a1)XCTAssertNotNil((a1), @"")#define ml_assertEqual(a1, a2)XCTAssertEqual((a1),(a2), @"")#define ml_assertEqualObjects(a1, a2)XCTAssertEqualObjects((a1),(a2), @"")#define ml_assertNotEqual(a1, a2)XCTAssertNotEqual((a1),(a2), @"")#define ml_assertNotEqualObjects(a1, a2)XCTAssertNotEqualObjects((a1),(a2), @"")#define ml_assertAccuracy(a1, a2, acc)XCTAssertEqualWithAccuracy((a1),(a2),(acc))
簡單實用準備工作
795875-a207ce29f12fa7f2.png.jpeg
如果是之前創建的項目,里面沒有勾選,可以自己創建:
795875-9e3ebd787a699757.png.jpeg
進入到這個類,
setUp是每個測試方法調用前執行,
tearDown是每個測試方法調用后執行。
testExample是測試方法,和我們新建的沒有差別。
不過測試方法必須testXXX的格式,且不能有參數,不然不會識別為測試方法。測試方法的執行順序是字典序排序。
按快捷鍵Command + U進行單元測試,這個快捷鍵是全部測試。
795875-362afa43ea505bb6.png.jpeg
一、簡單使用單元測試
1、創建一個Person類,里面有一個test1類方法
2、順便勾選右邊的測試單元按鈕,為了對這個.m文件的數據測試
795875-192f3a273594f9e7.png.jpeg
3、如果項目比較小,就可以直接在項目剛創建的tests方法里測試,如果項目比較大的話,就創建一個單獨的tests,例如:PersonTets:
795875-89721ddcf3d48789.png.jpeg
4、創建一個測試方法:testPerson:
795875-cbeeb6ba7a19c6ef.png.jpeg
上面也說了:測試方法必須testXXX的格式,且不能有參數,不然不會識別為測試方法
如果前面不出現小菱形,可以Build一下
5、點擊小菱形圖標,出現綠色說明成功:
795875-fdefc42918aaccb4.png.jpeg
6、有時候需要驗證值的話,就使用 XCTAssert(<#expression, ...#>),或者其他的斷言方法測試結果;
二、對方法引用AFN框架的單元測試
1、測試帶有網絡請求的方法,例如:
795875-12be468144d6e31a.png.jpeg
2、 按照之前的那樣 [Person test2]的話,點擊小菱角會出現找到AFN框架:
795875-d4d646284ee3dd06.png.jpeg
3、需要做一些配置
3.1、復制Target(App) - Build Setting - Header Search Paths 的路徑。
795875-27159a355bc9a14f.png.jpeg
3.2、粘貼到Target(UnitTests) - Build Setting - Header - Search Paths里。
3.3、復制Target(App) - Build Setting - User-Defined - PODS_ROOT整條。
3.4、到Target(UnitTests) - Build Setting - User-Defined新建一條PODS_ROOT。
795875-601bbddba0354ed1.png.jpeg
4、OK了!
三、期望
期望實際上是異步測試,當測試異步方法時,因為結果并不是立刻獲得,所以我們可以設置一個期望,期望是有時間限定的的,fulfill表示滿足期望。
例如
- (void)testAsynExample {// 1. 創建期望XCTestExpectation *exp = [selfexpectationWithDescription:@"這里可以是操作出錯的原因描述。。。"];NSOperationQueue*queue = [[NSOperationQueuealloc]init];? ? [queue addOperationWithBlock:^{//模擬:這個異步操作需要2秒后才能獲取結果,比如一個異步網絡請求sleep(2);//模擬:獲取的異步操作后,獲取結果,判斷異步方法的結果是否正確XCTAssertEqual(@"a",@"a");//2. 如果斷言沒問題,就調用fulfill宣布測試滿足[exp fulfill];? ? }];//3. 設置延遲多少秒后,如果沒有滿足測試條件就報錯[selfwaitForExpectationsWithTimeout:3handler:^(NSError* _Nullable error) {if(error) {NSLog(@"Timeout Error: %@", error);? ? ? ? }? ? }];}
這個測試肯定是通過的,因為設置延遲為3秒,而異步操作2秒就除了一個正確的結果,并宣布了條件滿足 [exp fulfill],但是當我們把延遲改成1秒,這個測試用例就不會成功,錯誤原因是 expectationWithDescription:@"這里可以是操作出錯的原因描述。。。
異步測試除了使用expectationWithDescription
以外,還可以使用expectationForPredicate和expectationForNotification
,
掌握expectationWithDescription
即可,后兩者者僅僅了解
下面這個例子使用expectationForPredicate 測試方法,代碼來自于AFNetworking,用于測試backgroundImageForState方法
- (void)testThatBackgroundImageChanges {? ? XCTAssertNil([self.buttonbackgroundImageForState:UIControlStateNormal]);NSPredicate*predicate = [NSPredicatepredicateWithBlock:^BOOL(UIButton* _Nonnull button,NSDictionary * _Nullable bindings) {return[button backgroundImageForState:UIControlStateNormal] !=nil;? ? }];? ? [selfexpectationForPredicate:predicate? ? ? ? ? ? ? evaluatedWithObject:self.buttonhandler:nil];? ? [selfwaitForExpectationsWithTimeout:20handler:nil];}
利用謂詞計算,button是否正確的獲得了backgroundImage,如果正確20秒內正確獲得則通過測試,否則失敗。
expectationForNotification 方法 ,該方法監聽一個通知,如果在規定時間內正確收到通知則測試通過。
- (void)testAsynExample1 {? ? [selfexpectationForNotification:(@"監聽通知的名稱xxx") object:nilhandler:nil];? ? [[NSNotificationCenterdefaultCenter]postNotificationName:@"監聽通知的名稱xxx"object:nil];//設置延遲多少秒后,如果沒有滿足測試條件就報錯[selfwaitForExpectationsWithTimeout:3handler:nil];}
這個例子也可以用expectationWithDescription實現,只是多些很多代碼而已,但是這個可以幫助你更好的理解expectationForNotification 方法和 expectationWithDescription 的區別。同理,expectationForPredicate方法也可以使用expectationWithDescription實現。
functestAsynExample1(){letexpectation = expectationWithDescription("監聽通知的名稱xxx")letsub =NSNotificationCenter.defaultCenter().addObserverForName("監聽通知的名稱xxx", object:nil, queue:nil) { (not) ->Voidinexpectation.fulfill()? ? }NSNotificationCenter.defaultCenter().postNotificationName("監聽通知的名稱xxx", object:nil)? ? waitForExpectationsWithTimeout(1, handler:nil)NSNotificationCenter.defaultCenter().removeObserver(sub)}
四、期望實戰:
-(void)testRequest{# 創建期望XCTestExpectation *expectation =[selfexpectationWithDescription:@"沒有滿足期望"];? ? AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager manager];? ? sessionManager.responseSerializer= [AFHTTPResponseSerializer serializer];? ? [sessionManager GET:@"http://www.weather.com.cn/adat/sk/101110101.html"parameters:nilsuccess:^(NSURLSessionDataTask* _Nonnull task,id_Nullable responseObject) {NSLog(@"responseObject:%@", [NSJSONSerializationJSONObjectWithData:responseObject options:1error:nil]);# 不為nil 通過,XCTAssertNotNil(responseObject,@"返回出錯");# 滿意[expectation fulfill];? ? } failure:^(NSURLSessionDataTask* _Nullable task,NSError* _Nonnull error) {# 為nil 通過,XCTAssertNil(error,@"請求出錯");? ? }];# 設置5秒的超時時間[selfwaitForExpectationsWithTimeout:3.0handler:^(NSError*error) {if(error) {NSLog(@"Timeout Error: %@", error);? ? ? ? }? ? }];}