28
iOS Unit testing II Liyao Chen @ KKBOX

iOS Unit testing II

Embed Size (px)

Citation preview

Page 1: iOS Unit testing II

iOS Unit testing II

Liyao Chen @ KKBOX

Page 2: iOS Unit testing II

Outlines

• External dependency

• 透過 Protocol 依賴注⼊入解套

• 透過隔離框架解套

Page 3: iOS Unit testing II

External dependency

Page 4: iOS Unit testing II

–Roy Osherove

是系統中的⼀一個對象, 被測試的程式碼會與其互動,但是你不能控制它。

External dependency

(⾦金迎 譯)

Page 5: iOS Unit testing II

External dependency

(

Page 6: iOS Unit testing II

External dependency

你跟著師⽗父去幫別⼈人佈線安裝電燈,師⽗父三兩下就搞定了,然後請你確認有沒有裝好,請問你會怎麼做?

打開開關看看電燈有沒有跟著亮再關上開關看電燈有沒有跟著暗

Page 7: iOS Unit testing II

External dependency

⼀一個⽉月後客⼾戶打來說電燈打不開,請你們去檢查,你第⼀一時間會想到的問題是什麼?

可能開關壞掉了?

可能電線沒接好?

可能電線壞掉了?

Page 8: iOS Unit testing II

External dependency

可能電線壞掉了?

可能開關壞掉了?

可能電線沒接好?

Page 9: iOS Unit testing II

External dependency

Page 10: iOS Unit testing II

External dependency

static NSTimeInterval const yearInterval = 60*60*24*30*12;

@interface LCWire () @property (strong, nonatomic) NSDate *expiryDate; @end

@implementation LCWire

- (instancetype)init { if(self = [super init]){

// 製造⽇日期為 2010/10/10 NSDateComponents *comps = [[NSDateComponents alloc] init]; [comps setDay:10]; [comps setMonth:10]; [comps setYear:2010]; NSDate *manufacturedDate = [[NSCalendar currentCalendar] dateFromComponents:comps]; // 有效期限為兩年 _expiryDate = [manufacturedDate dateByAddingTimeInterval:yearInterval*2]; } return self; }

- (BOOL)isExpired { return [[NSDate date] timeIntervalSinceReferenceDate] > [self.expiryDate timeIntervalSinceReferenceDate]; }

@end

Page 11: iOS Unit testing II

Break dependency

Page 12: iOS Unit testing II

Break dependency

Dummy class

Fake

Page 13: iOS Unit testing II

Dummy class

A class that implements an interface but contains fixed data and no logic.

– Sangdol

Page 14: iOS Unit testing II

Dummy class

DEMO1. Extract protocol

2. Use protocol injection

3. Dummy class

Page 15: iOS Unit testing II

Dummy class// LCWire.h @protocol Expirable <NSObject> - (BOOL)isExpired; @end

@interface LCWire : NSObject <Expirable> - (BOOL)isExpired; @end

// LCRoomTests.m // 永遠不會過期的電線 @interface LCNeverExpiredWire : NSObject <Expirable> @end @implementation LCNeverExpiredWire - (BOOL)isExpired { return NO; } @end

@interface LCRoomTests : XCTestCase @end @implementation LCRoomTests

- (void)testLightOnInit { id<Expirable> wire = [[LCNeverExpiredWire alloc] init]; LCRoom *room = [[LCRoom alloc] initWithLight:YES wire:wire]; XCTAssertTrue(room.isLight); } @end

Page 16: iOS Unit testing II

Dummy class

- (void)testLightOnInit { LCWire *wire = [[LCWire alloc] init]; LCRoom *room = [[LCRoom alloc] initWithLight:YES wire:wire]; XCTAssertTrue(room.isLight); }

- (void)testLightOnInit { id<Expirable> wire = [[LCNeverExpiredWire alloc] init]; LCRoom *room = [[LCRoom alloc] initWithLight:YES wire:wire]; XCTAssertTrue(room.isLight); }

Page 17: iOS Unit testing II

Dummy class

Page 18: iOS Unit testing II

Break dependency

那那些不能透過軟體設計控制的怎麼辦?

Page 19: iOS Unit testing II

Break dependency

Mock

OCMock

Page 20: iOS Unit testing II

Mock object

Page 21: iOS Unit testing II

Mock - PartialMock()

- (void)testLightOnInit { // Mock ⼀一個永遠不會過期的電線 LCWire *wire = [[LCWire alloc] init]; id mockWire = OCMPartialMock(wire); OCMStub([mockWire isExpired]).andReturn(NO); LCRoom *room = [[LCRoom alloc] initWithLight:YES wire:wire]; XCTAssertTrue(room.isLight); }

Page 22: iOS Unit testing II

Mock

1. Mock [NSDate date]

2. Mock [NSUserDefault standardDefault]

3. Mock session

4. Mock different user in a test case

Page 23: iOS Unit testing II

Mock singleton- (void)testMockDate { NSDate *now = [NSDate date]; NSDateComponents *comps = [[NSDateComponents alloc] init]; [comps setDay:10]; [comps setMonth:10]; [comps setYear:2010]; NSDate *speficDate = [[NSCalendar currentCalendar] dateFromComponents:comps]; id mockDate = OCMClassMock([NSDate class]); OCMStub([mockDate date]).andReturn(speficDate); // Mock [NSDate date] 讓他回傳指定⽇日期, 也就是 Mock 現在時間 NSLog(@"now: %@, mockDate: %@",now, [NSDate date]); }

- (void)testMockUserdefault { NSString *name = [[NSUserDefaults standardUserDefaults] objectForKey:@"userName"]; id mockUserDefault = OCMPartialMock([NSUserDefaults standardUserDefaults]); OCMStub([mockUserDefault objectForKey:@"userName"]).andReturn(@"Liyao Chen"); NSString *mockName = [[NSUserDefaults standardUserDefaults] objectForKey:@"userName"]; NSLog(@"name: %@, mockName: %@", name, mockName); }

Page 24: iOS Unit testing II

Summary

• 發現程式缺陷才能解決

• 善⽤用依賴注⼊入改善程式架構

• 當測試有多個Mock時重新檢視設計

Page 25: iOS Unit testing II

QnA

• API測試不是後端的⼯工作嗎?

• 什麼東⻄西⼀一定要測?

Page 26: iOS Unit testing II

We are hiring!

@ KKBOX

Page 27: iOS Unit testing II

Reference

• http://stackoverflow.com/questions/346372/whats-the-difference-between-faking-mocking-and-stubbing

Page 28: iOS Unit testing II

Other links

• Sample codehttps://github.com/gliyao/LCUnitTestsExample