ios testing

44
iOS Testing Derrick Chao

Upload: derrick-chao

Post on 14-Jan-2017

394 views

Category:

Documents


4 download

TRANSCRIPT

Page 1: iOS Testing

iOS TestingDerrick Chao

Page 2: iOS Testing

Frameworks

• XCTest

• OCMock

• KIF

Page 3: iOS Testing

XCTest• Xcode 內建測試框架

• ⽅方法以 test 開頭

- (void)testExample { // This is an example of a functional test case. XCTAssert(YES, @"Pass"); XCTAssert(1+1==2); XCTAssertTrue(1+1==2); XCTAssertFalse(1+1==3); }

• XCTAssert XCTAssert(expression, …); XCTAssertTrue(expression, …); XCTAssertFalse(expression, ...);

Page 4: iOS Testing

Unit Test Example- (void)testValidatePassword { NSString *password1 = @"AbCd1234"; NSString *password2 = @"AbCd12"; NSString *password3 = @"ABCD"; NSString *password4 = @"AbCd1"; NSString *password5 = @""; XCTAssertTrue([Helper validatePassword:password1]); XCTAssertTrue([Helper validatePassword:password2]); XCTAssertFalse([Helper validatePassword:password3]); XCTAssertFalse([Helper validatePassword:password4]); XCTAssertFalse([Helper validatePassword:password5]); }

Page 5: iOS Testing

Test Async Function Example

• 寄⼀一則 message 給另⼀一個 user 的 function

• async

• block callback

Page 6: iOS Testing

Test Async Function Example

- (void)testSendMessageWithNilMessage { XCTestExpectation *expectation =

[self expectationWithDescription:@"completion should be called"]; [Message sendMessage:nil recipientInfo:nil completion:^(id responseObject, NSError *error) { [expectation fulfill]; XCTAssertNotNil(error); XCTAssert([error.userInfo[@"message"] isEqualToString:@"message can't be nil"]); }]; [self waitForExpectationsWithTimeout:2 handler:nil]; }

Page 7: iOS Testing

Singleton// LoopdManager.h file + (instancetype)sharedManager;

// LoopdManager.m file + (instancetype)sharedManager { static dispatch_once_t onceToken; static LoopdManager *sharedInstance; dispatch_once(&onceToken, ^{ sharedInstance = [[LoopdManager alloc] init]; }); return sharedInstance; }

Page 8: iOS Testing

Test Singleton Example

- (void)testSharedManager { LoopdManager *manager1 = [LoopdManager sharedManager]; LoopdManager *manager2 = [LoopdManager sharedManager]; LoopdManager *manager3 = [LoopdManager new]; XCTAssertNotNil(manager1); XCTAssertNotNil(manager2); XCTAssertEqual(manager1, manager2); XCTAssertNotEqual(manager1, manager3); }

Page 9: iOS Testing

OCMock• 建造⼀一個虛擬的對象物件,可以⾃自訂回傳值

- (void)testMock { id mockUserInfo = OCMClassMock([UserInfo class]); // fullname should be nil for now XCTAssertNil([mockUserInfo fullname]); // make a custom return value OCMStub([mockUserInfo fullname]).andReturn(@"David"); // fullname should be David now XCTAssert([[mockUserInfo fullname] isEqualToString:@"David"]); }

Page 10: iOS Testing

OCMock Example (1)ListViewController 有個 collection view viewDidLoad 時從 server 上 find all SomeModel, 並 show 出來

// in SomeModel.h file@interface SomeModel : ModelObject

+ (void)findAllByCurrentUserId:(NSString *)currentUserId completion:(ArrayResultBlock)completion;

@end

Page 11: iOS Testing

OCMock Example (2)- (void)testCollectionViewCellNumber { id mockSomeModel = OCMClassMock([SomeModel class]); OCMStub([mockSomeModel findAllRelationshipsByCurrentUserId:[OCMArg any] completion:[OCMArg any]]).andDo(^(NSInvocation *invocation) { ArrayResultBlock completion; [invocation getArgument:&completion atIndex: 3]; NSArray *someModels = @[@“1”, @“2”, @“3”, @“4”, @“5”]; completion(someModels, nil); }); ListViewController *listVC = [ListViewController new]; listVC.view; NSInteger num = [listVC collectionView:nil numberOfItemsInSection:0]; XCTAssert(num == 5); XCTAssert(num != 6); }

Page 12: iOS Testing

KIF• KIF iOS Integration Testing Framework

Page 13: iOS Testing
Page 14: iOS Testing

KIF Example@interface UITests : KIFTestCase

@end

@implementation UITests

- (void)testLoginSteps { [tester tapViewWithAccessibilityLabel:@"regular_button"]; [tester enterText:@"[email protected]" intoViewWithAccessibilityLabel:@"emailTextField"]; [tester enterText:@"abcdefg" intoViewWithAccessibilityLabel:@"passwordTextField"]; [tester tapViewWithAccessibilityLabel:@“checkmark_button"]; }

@end

Page 15: iOS Testing

MVC & MVP

Page 16: iOS Testing

MVC• view 可透過 controller 取得 model 資料

• 顯⽰示 UI 的邏輯寫在 view 裡⾯面

• 耦合較⾼高

Page 17: iOS Testing

MVC & MVP

Page 18: iOS Testing

MVP• 由 MVC 演變⽽而來

• View 無法直接取得 model, 資料傳遞需透過 presenter

• 耦合較低

Page 19: iOS Testing

MVP 的優點• view 和 model 完全分離, 修改

view 不會影響到 model

• ⼀一個 presenter 可以⽤用於多個 view, 不需改變 presenter 邏輯

• model 取得的邏輯在 presenter 內, 所以不需要 view 即可做 unit test

Page 20: iOS Testing

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath]; // config cell Car *car = self.cars[indexPath.item]; cell.makeLabel.text = car.make; cell.modelLabel.text = car.model;

cell.yearLabel.text = car.year; cell.detailLabel.text = car.detail;

return cell; }

Page 21: iOS Testing

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath]; // config cell Car *car = self.cars[indexPath.item]; cell.car = car;

[cell showUI]; return cell; }

Page 22: iOS Testing
Page 23: iOS Testing

Test Cell- (void)testCollectionViewCellNumber { id mockSomeModel = OCMClassMock([SomeModel class]); OCMStub([mockSomeModel findAllRelationshipsByCurrentUserId:[OCMArg any] completion:[OCMArg any]]).andDo(^(NSInvocation *invocation) { ArrayResultBlock completion; [invocation getArgument:&completion atIndex: 3]; NSArray *someModels = @[mod1, mod2, mod3, mod4, mod5]; completion(someModels, nil); }); ListViewController *listVC = [ListViewController new]; listVC.view; NSInteger num = [listVC collectionView:nil numberOfItemsInSection:0]; XCTAssert(num == 5); XCTAssert(num != 6);

UICollectionViewCell *cell = [contactListVC collectionView:nil cellForItemAtIndexPath:0];

}

Page 24: iOS Testing

為什麼 Apple 說 iOS app 是 MVC?

Page 25: iOS Testing
Page 26: iOS Testing

• 在開發 iOS app 的時候, view 和 model 並沒有直接的關係

• 不會在 view 中寫取得 model 的 code

Page 27: iOS Testing

Spike Solution

Page 28: iOS Testing

Spike Solution

• 不確定功能如何設計

• 不確定此功能是否符合需求

Page 29: iOS Testing

Spike Solution (2)• A spike solution is dirty and quick.

Code just enough to get the answer you need.

• Throw it away when you’re done with it.

Page 30: iOS Testing

Spike Solution (3)• 開個新的 branch

• 在這個 branch 內測試新功能

• 結束以後切回原本 branch,不要 merge 回去,測試的 code 不應該出現在 production code 中

Page 31: iOS Testing

TDD with Xcode• 需求: 想要有個做加法運算的 Helper

Page 32: iOS Testing

TDD with Xcode (2)• 建出缺少的類別和⽅方法

Page 33: iOS Testing

TDD with Xcode (3)• 可以將 Xcode 選擇分割畫⾯面, 並將右邊畫⾯面選 Test Classes

Page 34: iOS Testing

TDD with Xcode (4)• command-U

Page 35: iOS Testing

TDD with Xcode (5)• 驗證加法運算是否正確

Page 36: iOS Testing

TDD with Xcode (6)• 補完 function, 然後再測試⼀一次

Page 37: iOS Testing

⼀一開始我對 TDD 的感覺

Page 38: iOS Testing

Testing or TDD 的好處• test => spec

• 確保新的 code 不會造成舊的 code 出現 bug

• 同伴或下⼀一個接⼿手的⼈人,不會⽤用錯誤的⽅方式使⽤用。修改舊的 code 也較有信⼼心

• 幫助程式寫得更好更乾淨

• 提⾼高測試的涵蓋率

Page 39: iOS Testing

使⽤用 TDD 之後

Page 40: iOS Testing

使⽤用 TDD 之後

Page 41: iOS Testing

使⽤用 TDD 之後

Page 42: iOS Testing

The End

Page 43: iOS Testing

備註• sorry 當天我忘了 Q&A 時間, 所以有任何問題都可以 email 給我 => [email protected] , 我們可以互相討論!

• 或者可以加⼊入 iOS Developer Slack 群組, 我的 id是 wasdplayer, 可以直接私訊我!裡⾯面有有超多的 iOS 開發者交流各種訊息, 是個不錯的地⽅方!

• http://ios-developers.io 按這裡邀請你⾃自⼰己加⼊入 Slack 群組!

Page 44: iOS Testing

參考資料• http://iosunittesting.com

• http://stackoverflow.com/questions/1053406/why-does-apple-say-iphone-apps-use-mvc

• http://blog.sanc.idv.tw/2011/09/uimvp-passive-view.html