More Related Content Similar to iOS Testing (20) iOS Testing3. 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]);
}
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);
}
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;
}
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];
}
26. • 在開發 iOS app 的時候, view 和 model
並沒有直接的關係
• 不會在 view 中寫取得 model 的 code
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 中
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 群
組!