- 簡單使用單元測試
- 對方法引用AFN框架的單元測試
不寫單元測試的程序員是不合格的器虾,為了讓自己成為一名合格的程序員,學(xué)習(xí)如何寫單元測試是很有必要的鼎俘,這里以Xcode集成的測試框架XCTest為例似舵。
XCTest基礎(chǔ)用法
默認(rèn)的測試類繼承自XCTestCase,當(dāng)然也可以自定義測試類霍狰,添加一些公共的輔助方法抡草,所有的測試方法都必須以test開頭,且不能有參數(shù)蔗坯,不然不會識別為測試方法康震。
@interface __Tests : XCTestCase
@end
@implementation __Tests
// 在每一個(gè)測試用例開始前調(diào)用,用來初始化相關(guān)數(shù)據(jù)
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
// 在測試用例完成后調(diào)用宾濒,可以用來釋放變量等結(jié)尾操作
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
// 測試方法
- (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中方法執(zhí)行的時(shí)間,比對設(shè)定的標(biāo)準(zhǔn)值和偏差覺得是否可以通過測試
- (void)testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
}];
}
/// 我自定義的 針對 Person 類的測試方法
- (void)testPerson{
}
@end
斷言
XCTest的斷言具體可查閱XCTestAssertions.h文件,這里還是做個(gè)簡單的總結(jié)
//通用斷言
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...)
舉個(gè)例子
- (void)testExample {
//設(shè)置變量和設(shè)置預(yù)期值
NSUInteger a = 10;NSUInteger b = 15;
NSUInteger expected = 24;
//執(zhí)行方法得到實(shí)際值
NSUInteger actual = [self add:a b:b];
//斷言判定實(shí)際值和預(yù)期是否符合
XCTAssertEqual(expected, actual,@"add方法錯(cuò)誤答姥!");
}
-(NSUInteger)add:(NSUInteger)a b:(NSUInteger)b{
return a+b;
}
從這也能看出一個(gè)測試用例比較規(guī)范的寫法,1:定義變量和預(yù)期谚咬,2:執(zhí)行方法得到實(shí)際值鹦付,3:斷言
當(dāng)然在有些特殊情況下直接使用這些斷言,會讓代碼看起來很臃腫,使用時(shí)可以對其進(jìn)行一定的封裝一下:
#define ml_ssertTrue(expr) XCTAssertTrue((expr), @"設(shè)置指定的字符串择卦,同下")
#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))
簡單實(shí)用準(zhǔn)備工作
如果是之前創(chuàng)建的項(xiàng)目敲长,里面沒有勾選,可以自己創(chuàng)建:
進(jìn)入到這個(gè)類秉继,
- setUp是每個(gè)測試方法調(diào)用前執(zhí)行祈噪,
- tearDown是每個(gè)測試方法調(diào)用后執(zhí)行。
- testExample是測試方法尚辑,和我們新建的沒有差別辑鲤。
- 不過測試方法必須testXXX的格式,且不能有參數(shù)杠茬,不然不會識別為測試方法月褥。測試方法的執(zhí)行順序是字典序排序。
- 按快捷鍵Command + U進(jìn)行單元測試瓢喉,這個(gè)快捷鍵是全部測試宁赤。
一、簡單使用單元測試
1栓票、創(chuàng)建一個(gè)Person類决左,里面有一個(gè)test1類方法
2、順便勾選右邊的測試單元按鈕走贪,為了對這個(gè).m文件的數(shù)據(jù)測試
3佛猛、如果項(xiàng)目比較小,就可以直接在項(xiàng)目剛創(chuàng)建的tests方法里測試厉斟,如果項(xiàng)目比較大的話挚躯,就創(chuàng)建一個(gè)單獨(dú)的tests,例如:PersonTets:
4擦秽、創(chuàng)建一個(gè)測試方法:testPerson:
上面也說了:測試方法必須testXXX的格式码荔,且不能有參數(shù),不然不會識別為測試方法
如果前面不出現(xiàn)小菱形感挥,可以Build一下
5缩搅、點(diǎn)擊小菱形圖標(biāo),出現(xiàn)綠色說明成功:
6触幼、有時(shí)候需要驗(yàn)證值的話硼瓣,就使用 XCTAssert(<#expression, ...#>),或者其他的斷言方法測試結(jié)果;
二堂鲤、對方法引用AFN框架的單元測試
1亿傅、測試帶有網(wǎng)絡(luò)請求的方法,例如:
2瘟栖、 按照之前的那樣 [Person test2]的話葵擎,點(diǎn)擊小菱角會出現(xiàn)找到AFN框架:
3、需要做一些配置
3.1半哟、復(fù)制Target(App) - Build Setting - Header Search Paths 的路徑酬滤。
3.2、粘貼到Target(UnitTests) - Build Setting - Header - Search Paths里寓涨。
3.3盯串、復(fù)制Target(App) - Build Setting - User-Defined - PODS_ROOT整條。
3.4戒良、到Target(UnitTests) - Build Setting - User-Defined新建一條PODS_ROOT体捏。
4、OK了蔬墩!
三译打、期望
期望實(shí)際上是異步測試,當(dāng)測試異步方法時(shí)拇颅,因?yàn)榻Y(jié)果并不是立刻獲得奏司,所以我們可以設(shè)置一個(gè)期望,期望是有時(shí)間限定的的樟插,fulfill表示滿足期望韵洋。
例如
- (void)testAsynExample {
// 1. 創(chuàng)建期望
XCTestExpectation *exp = [self expectationWithDescription:@"這里可以是操作出錯(cuò)的原因描述。黄锤。搪缨。"];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
//模擬:這個(gè)異步操作需要2秒后才能獲取結(jié)果,比如一個(gè)異步網(wǎng)絡(luò)請求
sleep(2);
//模擬:獲取的異步操作后鸵熟,獲取結(jié)果副编,判斷異步方法的結(jié)果是否正確
XCTAssertEqual(@"a", @"a");
//2. 如果斷言沒問題,就調(diào)用fulfill宣布測試滿足
[exp fulfill];
}];
//3. 設(shè)置延遲多少秒后流强,如果沒有滿足測試條件就報(bào)錯(cuò)
[self waitForExpectationsWithTimeout:3 handler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"Timeout Error: %@", error);
}
}];
}
這個(gè)測試肯定是通過的痹届,因?yàn)樵O(shè)置延遲為3秒,而異步操作2秒就除了一個(gè)正確的結(jié)果打月,并宣布了條件滿足 [exp fulfill]队腐,但是當(dāng)我們把延遲改成1秒,這個(gè)測試用例就不會成功奏篙,錯(cuò)誤原因是 expectationWithDescription:@"這里可以是操作出錯(cuò)的原因描述柴淘。。。
異步測試除了使用expectationWithDescription
以外为严,還可以使用expectationForPredicate和expectationForNotification
,
掌握expectationWithDescription
即可敛熬,后兩者者僅僅了解
下面這個(gè)例子使用expectationForPredicate 測試方法,代碼來自于AFNetworking第股,用于測試backgroundImageForState方法
- (void)testThatBackgroundImageChanges {
XCTAssertNil([self.button backgroundImageForState:UIControlStateNormal]);
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(UIButton * _Nonnull button, NSDictionary<NSString *,id> * _Nullable bindings) {
return [button backgroundImageForState:UIControlStateNormal] != nil;
}];
[self expectationForPredicate:predicate
evaluatedWithObject:self.button
handler:nil];
[self waitForExpectationsWithTimeout:20 handler:nil];
}
利用謂詞計(jì)算荸型,button是否正確的獲得了backgroundImage,如果正確20秒內(nèi)正確獲得則通過測試炸茧,否則失敗。
expectationForNotification 方法 ,該方法監(jiān)聽一個(gè)通知,如果在規(guī)定時(shí)間內(nèi)正確收到通知則測試通過稿静。
- (void)testAsynExample1 {
[self expectationForNotification:(@"監(jiān)聽通知的名稱xxx") object:nil handler:nil];
[[NSNotificationCenter defaultCenter]postNotificationName:@"監(jiān)聽通知的名稱xxx" object:nil];
//設(shè)置延遲多少秒后梭冠,如果沒有滿足測試條件就報(bào)錯(cuò)
[self waitForExpectationsWithTimeout:3 handler:nil];
}
這個(gè)例子也可以用expectationWithDescription實(shí)現(xiàn),只是多些很多代碼而已,但是這個(gè)可以幫助你更好的理解expectationForNotification 方法和 expectationWithDescription 的區(qū)別改备。同理控漠,expectationForPredicate方法也可以使用expectationWithDescription實(shí)現(xiàn)。
func testAsynExample1() {
let expectation = expectationWithDescription("監(jiān)聽通知的名稱xxx")
let sub = NSNotificationCenter.defaultCenter().addObserverForName("監(jiān)聽通知的名稱xxx", object: nil, queue: nil) { (not) -> Void in
expectation.fulfill()
}
NSNotificationCenter.defaultCenter().postNotificationName("監(jiān)聽通知的名稱xxx", object: nil)
waitForExpectationsWithTimeout(1, handler: nil)
NSNotificationCenter.defaultCenter().removeObserver(sub)
}
四悬钳、期望實(shí)戰(zhàn):
-(void)testRequest{
# 創(chuàng)建期望
XCTestExpectation *expectation =[self expectationWithDescription:@"沒有滿足期望"];
AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager manager];
sessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
[sessionManager GET:@"http://www.weather.com.cn/adat/sk/101110101.html" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"responseObject:%@", [NSJSONSerialization JSONObjectWithData:responseObject options:1 error:nil]);
# 不為nil 通過盐捷,
XCTAssertNotNil(responseObject, @"返回出錯(cuò)");
# 滿意
[expectation fulfill];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
# 為nil 通過,
XCTAssertNil(error, @"請求出錯(cuò)");
}];
# 設(shè)置5秒的超時(shí)時(shí)間
[self waitForExpectationsWithTimeout:3.0 handler:^(NSError *error) {
if (error) {
NSLog(@"Timeout Error: %@", error);
}
}];
}