SlideShare a Scribd company logo
1 of 44
Download to read offline
iOS Testing
Derrick Chao
Frameworks
• XCTest
• OCMock
• KIF
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, ...);
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]);
}
Test Async Function
Example
• 寄⼀一則 message 給另⼀一個 user 的
function
• async
• block callback
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];
}
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;
}
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);
}
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"]);
}
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
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);
}
KIF
• KIF iOS Integration Testing Framework


KIF Example
@interface UITests : KIFTestCase
@end
@implementation UITests
- (void)testLoginSteps {
[tester tapViewWithAccessibilityLabel:@"regular_button"];
[tester enterText:@"123@123.com" intoViewWithAccessibilityLabel:@"emailTextField"];
[tester enterText:@"abcdefg" intoViewWithAccessibilityLabel:@"passwordTextField"];
[tester tapViewWithAccessibilityLabel:@“checkmark_button"];
}
@end
MVC & MVP
MVC
• view 可透過 controller 取
得 model 資料
• 顯⽰示 UI 的邏輯寫在 view
裡⾯面
• 耦合較⾼高
MVC & MVP
MVP
• 由 MVC 演變⽽而來
• View 無法直接取得 model,
資料傳遞需透過 presenter
• 耦合較低
MVP 的優點
• view 和 model 完全分離, 修改
view 不會影響到 model
• ⼀一個 presenter 可以⽤用於多個
view, 不需改變 presenter 邏輯
• model 取得的邏輯在 presenter
內, 所以不需要 view 即可做 unit
test
- (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;
}
- (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;
}
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];
}
為什麼 Apple 說 iOS app 是
MVC?
• 在開發 iOS app 的時候, view 和 model
並沒有直接的關係
• 不會在 view 中寫取得 model 的 code
Spike Solution
Spike Solution
• 不確定功能如何設計
• 不確定此功能是否符合需求
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.
Spike Solution (3)
• 開個新的 branch
• 在這個 branch 內測試新功能
• 結束以後切回原本 branch,不要
merge 回去,測試的 code 不應該出現
在 production code 中
TDD with Xcode
• 需求: 想要有個做加法運算的 Helper
TDD with Xcode (2)
• 建出缺少的類別和⽅方法
TDD with Xcode (3)
• 可以將 Xcode 選擇分割畫⾯面, 並將右邊
畫⾯面選 Test Classes
TDD with Xcode (4)
• command-U
TDD with Xcode (5)
• 驗證加法運算是否正確
TDD with Xcode (6)
• 補完 function, 然後再測試⼀一次
⼀一開始我對 TDD 的感覺
Testing or TDD 的好處
• test => spec
• 確保新的 code 不會造成舊的 code
出現 bug
• 同伴或下⼀一個接⼿手的⼈人,不會⽤用錯誤
的⽅方式使⽤用。修改舊的 code 也較有
信⼼心
• 幫助程式寫得更好更乾淨
• 提⾼高測試的涵蓋率
使⽤用 TDD 之後
使⽤用 TDD 之後
使⽤用 TDD 之後
The End
備註
• sorry 當天我忘了 Q&A 時間, 所以有任何問題都可以 email
給我 => derrick@getloopd.com , 我們可以互相討論!
• 或者可以加⼊入 iOS Developer Slack 群組, 我的 id是
wasdplayer, 可以直接私訊我!裡⾯面有有超多的 iOS 開發
者交流各種訊息, 是個不錯的地⽅方!
• http://ios-developers.io 按這裡邀請你⾃自⼰己加⼊入 Slack 群
組!
參考資料
• 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

More Related Content

What's hot

Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Robot Media
 
JavaScript Proven Practises
JavaScript Proven PractisesJavaScript Proven Practises
JavaScript Proven Practises
Robert MacLean
 
Saving and retrieving_normal_modes_analysis_results
Saving and retrieving_normal_modes_analysis_resultsSaving and retrieving_normal_modes_analysis_results
Saving and retrieving_normal_modes_analysis_results
AltairKorea
 
Unit testing with Easymock
Unit testing with EasymockUnit testing with Easymock
Unit testing with Easymock
Ürgo Ringo
 

What's hot (20)

Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
 
Rechecking SharpDevelop: Any New Bugs?
Rechecking SharpDevelop: Any New Bugs?Rechecking SharpDevelop: Any New Bugs?
Rechecking SharpDevelop: Any New Bugs?
 
Jest
JestJest
Jest
 
JavaScript Proven Practises
JavaScript Proven PractisesJavaScript Proven Practises
JavaScript Proven Practises
 
Introduction to Active Record at MySQL Conference 2007
Introduction to Active Record at MySQL Conference 2007Introduction to Active Record at MySQL Conference 2007
Introduction to Active Record at MySQL Conference 2007
 
Notes for xx_use_serialgc
Notes for xx_use_serialgcNotes for xx_use_serialgc
Notes for xx_use_serialgc
 
Library system project file
Library system project fileLibrary system project file
Library system project file
 
Magic methods
Magic methodsMagic methods
Magic methods
 
Unit Testing: Special Cases
Unit Testing: Special CasesUnit Testing: Special Cases
Unit Testing: Special Cases
 
Saving and retrieving_normal_modes_analysis_results
Saving and retrieving_normal_modes_analysis_resultsSaving and retrieving_normal_modes_analysis_results
Saving and retrieving_normal_modes_analysis_results
 
Server1
Server1Server1
Server1
 
Basic Unit Testing with Mockito
Basic Unit Testing with MockitoBasic Unit Testing with Mockito
Basic Unit Testing with Mockito
 
Practical JavaScript Programming - Session 8/8
Practical JavaScript Programming - Session 8/8Practical JavaScript Programming - Session 8/8
Practical JavaScript Programming - Session 8/8
 
Orasta500.c
Orasta500.cOrasta500.c
Orasta500.c
 
Component lifecycle hooks in Angular 2.0
Component lifecycle hooks in Angular 2.0Component lifecycle hooks in Angular 2.0
Component lifecycle hooks in Angular 2.0
 
Maze
MazeMaze
Maze
 
Specs2
Specs2Specs2
Specs2
 
Practical JavaScript Programming - Session 1/8
Practical JavaScript Programming - Session 1/8Practical JavaScript Programming - Session 1/8
Practical JavaScript Programming - Session 1/8
 
Practical JavaScript Programming - Session 7/8
Practical JavaScript Programming - Session 7/8Practical JavaScript Programming - Session 7/8
Practical JavaScript Programming - Session 7/8
 
Unit testing with Easymock
Unit testing with EasymockUnit testing with Easymock
Unit testing with Easymock
 

Similar to iOS Testing

iOS for ERREST - alternative version
iOS for ERREST - alternative versioniOS for ERREST - alternative version
iOS for ERREST - alternative version
WO Community
 
J unit스터디슬라이드
J unit스터디슬라이드J unit스터디슬라이드
J unit스터디슬라이드
ksain
 

Similar to iOS Testing (20)

Testing (eng)
Testing (eng)Testing (eng)
Testing (eng)
 
iOS for ERREST - alternative version
iOS for ERREST - alternative versioniOS for ERREST - alternative version
iOS for ERREST - alternative version
 
Building stable testing by isolating network layer
Building stable testing by isolating network layerBuilding stable testing by isolating network layer
Building stable testing by isolating network layer
 
Unit testing en iOS @ MobileCon Galicia
Unit testing en iOS @ MobileCon GaliciaUnit testing en iOS @ MobileCon Galicia
Unit testing en iOS @ MobileCon Galicia
 
jasmine
jasminejasmine
jasmine
 
J unit스터디슬라이드
J unit스터디슬라이드J unit스터디슬라이드
J unit스터디슬라이드
 
淺談 iOS Unit test
淺談 iOS Unit test淺談 iOS Unit test
淺談 iOS Unit test
 
UI testing in Xcode 7
UI testing in Xcode 7UI testing in Xcode 7
UI testing in Xcode 7
 
CocoaHeads Moscow. Азиз Латыпов, VIPole. «Запросы в CoreData с агрегатными фу...
CocoaHeads Moscow. Азиз Латыпов, VIPole. «Запросы в CoreData с агрегатными фу...CocoaHeads Moscow. Азиз Латыпов, VIPole. «Запросы в CoreData с агрегатными фу...
CocoaHeads Moscow. Азиз Латыпов, VIPole. «Запросы в CoreData с агрегатными фу...
 
Testing the waters of iOS
Testing the waters of iOSTesting the waters of iOS
Testing the waters of iOS
 
Just Do It! ColdBox Integration Testing
Just Do It! ColdBox Integration TestingJust Do It! ColdBox Integration Testing
Just Do It! ColdBox Integration Testing
 
iOS testing
iOS testingiOS testing
iOS testing
 
I os 04
I os 04I os 04
I os 04
 
Cocoa heads testing and viewcontrollers
Cocoa heads testing and viewcontrollersCocoa heads testing and viewcontrollers
Cocoa heads testing and viewcontrollers
 
Sqlapi0.1
Sqlapi0.1Sqlapi0.1
Sqlapi0.1
 
«Objective-C Runtime в примерах» — Алексей Сторожев, e-Legion
«Objective-C Runtime в примерах» — Алексей Сторожев, e-Legion«Objective-C Runtime в примерах» — Алексей Сторожев, e-Legion
«Objective-C Runtime в примерах» — Алексей Сторожев, e-Legion
 
iOS best practices
iOS best practicesiOS best practices
iOS best practices
 
iOS Development Methodology
iOS Development MethodologyiOS Development Methodology
iOS Development Methodology
 
Describe's Full of It's
Describe's Full of It'sDescribe's Full of It's
Describe's Full of It's
 
Spock
SpockSpock
Spock
 

iOS Testing

  • 3. 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, ...);
  • 4. 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]); }
  • 5. Test Async Function Example • 寄⼀一則 message 給另⼀一個 user 的 function • async • block callback
  • 6. 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]; }
  • 7. 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; }
  • 8. 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); }
  • 9. 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"]); }
  • 10. 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
  • 11. 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); }
  • 12. KIF • KIF iOS Integration Testing Framework 

  • 13.
  • 14. KIF Example @interface UITests : KIFTestCase @end @implementation UITests - (void)testLoginSteps { [tester tapViewWithAccessibilityLabel:@"regular_button"]; [tester enterText:@"123@123.com" intoViewWithAccessibilityLabel:@"emailTextField"]; [tester enterText:@"abcdefg" intoViewWithAccessibilityLabel:@"passwordTextField"]; [tester tapViewWithAccessibilityLabel:@“checkmark_button"]; } @end
  • 16. MVC • view 可透過 controller 取 得 model 資料 • 顯⽰示 UI 的邏輯寫在 view 裡⾯面 • 耦合較⾼高
  • 18. MVP • 由 MVC 演變⽽而來 • View 無法直接取得 model, 資料傳遞需透過 presenter • 耦合較低
  • 19. MVP 的優點 • view 和 model 完全分離, 修改 view 不會影響到 model • ⼀一個 presenter 可以⽤用於多個 view, 不需改變 presenter 邏輯 • model 取得的邏輯在 presenter 內, 所以不需要 view 即可做 unit test
  • 20. - (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; }
  • 21. - (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; }
  • 22.
  • 23. 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]; }
  • 24. 為什麼 Apple 說 iOS app 是 MVC?
  • 25.
  • 26. • 在開發 iOS app 的時候, view 和 model 並沒有直接的關係 • 不會在 view 中寫取得 model 的 code
  • 28. Spike Solution • 不確定功能如何設計 • 不確定此功能是否符合需求
  • 29. 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.
  • 30. Spike Solution (3) • 開個新的 branch • 在這個 branch 內測試新功能 • 結束以後切回原本 branch,不要 merge 回去,測試的 code 不應該出現 在 production code 中
  • 31. TDD with Xcode • 需求: 想要有個做加法運算的 Helper
  • 32. TDD with Xcode (2) • 建出缺少的類別和⽅方法
  • 33. TDD with Xcode (3) • 可以將 Xcode 選擇分割畫⾯面, 並將右邊 畫⾯面選 Test Classes
  • 34. TDD with Xcode (4) • command-U
  • 35. TDD with Xcode (5) • 驗證加法運算是否正確
  • 36. TDD with Xcode (6) • 補完 function, 然後再測試⼀一次
  • 38. Testing or TDD 的好處 • test => spec • 確保新的 code 不會造成舊的 code 出現 bug • 同伴或下⼀一個接⼿手的⼈人,不會⽤用錯誤 的⽅方式使⽤用。修改舊的 code 也較有 信⼼心 • 幫助程式寫得更好更乾淨 • 提⾼高測試的涵蓋率
  • 43. 備註 • sorry 當天我忘了 Q&A 時間, 所以有任何問題都可以 email 給我 => derrick@getloopd.com , 我們可以互相討論! • 或者可以加⼊入 iOS Developer Slack 群組, 我的 id是 wasdplayer, 可以直接私訊我!裡⾯面有有超多的 iOS 開發 者交流各種訊息, 是個不錯的地⽅方! • http://ios-developers.io 按這裡邀請你⾃自⼰己加⼊入 Slack 群 組!