Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

iOS Unit testing II

1,679 views

Published on

外部依賴所造成的問題,用Protocol與mock的方式去破除依賴。

Published in: Software
  • Be the first to comment

iOS Unit testing II

  1. 1. iOS Unit testing II Liyao Chen @ KKBOX
  2. 2. Outlines • External dependency • 透過 Protocol 依賴注⼊入解套 • 透過隔離框架解套
  3. 3. External dependency
  4. 4. –Roy Osherove 是系統中的⼀一個對象, 被測試的程式碼會與其互動,但是你不能控制它。 External dependency (⾦金迎 譯)
  5. 5. External dependency (
  6. 6. External dependency 你跟著師⽗父去幫別⼈人佈線安裝電燈,師⽗父三兩下 就搞定了,然後請你確認有沒有裝好,請問你會 怎麼做? 打開開關看看電燈有沒有跟著亮
 再關上開關看電燈有沒有跟著暗
  7. 7. External dependency ⼀一個⽉月後客⼾戶打來說電燈打不開,請你們去檢 查,你第⼀一時間會想到的問題是什麼? 可能開關壞掉了? 可能電線沒接好? 可能電線壞掉了?
  8. 8. External dependency 可能電線壞掉了? 可能開關壞掉了? 可能電線沒接好?
  9. 9. External dependency
  10. 10. 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
  11. 11. Break dependency
  12. 12. Break dependency Dummy class Fake
  13. 13. Dummy class A class that implements an interface but contains fixed data and no logic. – Sangdol
  14. 14. Dummy class DEMO 1. Extract protocol 2. Use protocol injection 3. Dummy class
  15. 15. 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
  16. 16. 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); }
  17. 17. Dummy class
  18. 18. Break dependency 那那些不能透過軟體設 計控制的怎麼辦?
  19. 19. Break dependency Mock OCMock
  20. 20. Mock object
  21. 21. 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); }
  22. 22. Mock 1. Mock [NSDate date] 2. Mock [NSUserDefault standardDefault] 3. Mock session 4. Mock different user in a test case
  23. 23. 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); }
  24. 24. Summary • 發現程式缺陷才能解決 • 善⽤用依賴注⼊入改善程式架構 • 當測試有多個Mock時重新檢視設計
  25. 25. QnA • API測試不是後端的⼯工作嗎? • 什麼東⻄西⼀一定要測?
  26. 26. We are hiring! @ KKBOX
  27. 27. Reference • http://stackoverflow.com/questions/346372/ whats-the-difference-between-faking-mocking- and-stubbing
  28. 28. Other links • Sample code
 https://github.com/gliyao/LCUnitTestsExample

×