Testing on iOS

103e1ebcacd620770cf32a36b9aba17e?s=47 AppFoundry
December 10, 2015

Testing on iOS

A presentation by Joris Dubois (AppFoundry) for the December 2015 Mobel user group meetup

103e1ebcacd620770cf32a36b9aba17e?s=128

AppFoundry

December 10, 2015
Tweet

Transcript

  1. Your hosts Filip Maelbrancke Consultant @ AppFoundry filip.maelbrancke@appfoundry.be @fmaelbrancke Joris

    Dubois Consultant @ AppFoundry joris.dubois@appfoundry.be @DuboisJoris
  2. AppFoundry appfoundry.be

  3. None
  4. Patterns

  5. Dependency Injection a 25-dollar term for a 5-cent concept

  6. Coding Example DI: Spot the problem #import <Foundation/Foundation.h> @interface UserDefaultManager

    : NSObject - (void)registerLeetInteger; @end #import "UserDefaultManager.h" @implementation UserDefaultManager - (void)registerLeetInteger { [[[NSUserDefaults alloc] initWithSuiteName:@"LeetAppGroup"] setInteger:1337 forKey:@"leet"]; } @end
  7. Coding Example DI: Constructor Injection #import <Foundation/Foundation.h> @interface UserDefaultManager :

    NSObject - (instancetype)initWithUserDefaults:(NSUserDefaults *)userDefaults; - (void)registerLeetInteger; @end #import "UserDefaultManager.h" @implementation UserDefaultManager { NSUserDefaults *_userDefaults; } //Constructor Injection - (instancetype)initWithUserDefaults:(NSUserDefaults *)userDefaults { self = [super init]; if(self) { _userDefaults = userDefaults; } return self; } - (void)registerLeetInteger { [_userDefaults setInteger:1337 forKey:@"leet"]; } @end
  8. Coding Example DI: Method Injection #import <Foundation/Foundation.h> @interface UserDefaultManager :

    NSObject - (void)registerLeetIntegerOnUserDefaults:(NSUserDefaults *)userDefaults; @end #import "UserDefaultManager.h" @implementation UserDefaultManager //Method Injection - (void)registerLeetIntegerOnUserDefaults:(NSUserDefaults *)userDefaults { [userDefaults setInteger:1337 forKey:@"leet"]; } @end
  9. Coding Example DI: Property Injection #import <Foundation/Foundation.h> @interface UserDefaultManager :

    NSObject //Property Injection @property (nonatomic, strong) NSUserDefaults *userDefaults; - (void)registerLeetInteger; @end #import "UserDefaultManager.h" @implementation UserDefaultManager - (void)registerLeetInteger { [self.userDefaults setInteger:1337 forKey:@"leet"]; } @end
  10. MVVM

  11. MVC

  12. MASSIVE View Controller

  13. View Controller View Model MVVM View Controller View Model View

    Model
  14. Unit Testing frameworks & code coverage

  15. OCMock

  16. Coding Example OCMock: Mocking Class mock OCMClassMock([SomeClass class]); Protocol mock

    OCMProtocolMock(@protocol(SomeProtocol)); Strict class & protocol mock OCMStrictClassMock([SomeObject class]); OCMStrictProtocolMock(@protocol(SomeProtocol)); Partial mock OCMPartialMock(anObject); Observer mock OCMObserverMock()
  17. Coding Example OCMock: Stubbing OCMStub([mock someMethod]).andReturn(anObject); OCMStub([mock someMethodReturningABool]).andReturn(YES); OCMStub([mock someMethod]).andCall(anotherObject,

    @selector(aDifferentMethod)); OCMStub([mock someMethod]).andThrow(anException);
  18. Coding Example OCMock: Verification OCMVerify([mock someMethod]);

  19. Coding Example OCMock - (void)populateView:(UIView *)view withData:(id)data { PersonView *personView

    = (PersonView *)view; PersonObject *personObject = (PersonObject *)data; [_imageService fetchImageFromURL:personObject.imageURL fallBackImage:[UIImage personFallBackImage] forImageView:personView.personImageView]; } - (void)testPopulateViewWithDataDoesInvokeImageServiceForPersonImageViewWithExpectedFallbackImage { UIImageView *givenImageView = [[UIImageView alloc] init]; OCMExpect(_view.personImageView).andReturn(givenImageView); [_populator populateView:_view withData:_data]; OCMVerify([_imageService fetchImageFromURL:_data.imageURL fallBackImage:[UIImage personFallBackImage] forImageView:givenImageView]); }
  20. OCMockito personal favorite

  21. Coding Example OCMockito: Mocking Class mock mockClass([SomeClass class]); Protocol mock

    mockProtocol(@protocol(SomeProtocol)); mockProtocolWithoutOptionals(@protocol( SomeProtocol)); Class & protocol mock mockObjectAndProtocol([SomeObject class], @protocol(SomeProtocol));
  22. Coding Example OCMockito: Stubbing [given([mock someMethod]) willReturn:anObject]; [given([mock aMethodReturningABoolean]) willReturnBool:YES];

  23. Coding Example OCMockito: Verification [verify(mock) someMethod]; [verifyCount(mock, times(1)) someMethod]; [verifyCount(mock,

    never()) someMethod];
  24. Coding Example OCMockito - (void)populateView:(UIView *)view withData:(id)data { PersonView *personView

    = (PersonView *)view; PersonObject *personObject = (PersonObject *)data; [_imageService fetchImageFromURL:personObject.imageURL fallBackImage:[UIImage personFallBackImage] forImageView:personView.personImageView]; } - (void)testPopulateViewWithDataDoesInvokeImageServiceForPersonImageViewWithExpectedFallbackImage { UIImageView *givenImageView = [[UIImageView alloc] init]; [given(_view.personImageView) willReturn:givenImageView]; [_populator populateView:_view withData:_data]; [verify(_imageService) fetchImageFromURL:_data.imageURL fallBackImage:[UIImage personFallBackImage] forImageView:givenImageView]; }
  25. Expecta

  26. Coding Example Expecta Object matchers .conformsTo(@protocol(SomeProtocol)) .equal(anObject) .beIdenticalTo(anObject) .beNil() .beInstanceOf([SomeClass

    class]) Number matchers .beCloseTo(@3) .beGreaterThan(@3) .beGreaterThanOrEqualTo(@3) .beLessThan(@3) .beLessThanOrEqualTo(@3) .beFalsy() .beTruthy()
  27. Coding Example Expecta Text matchers .beginWith(@"someString") .endWith(@"someString") .match(@"regex") Collection matchers

    .contain(objectA, objectB) .beSupersetOf(objectA, objectB) .haveCountOf() .beEmpty() Logical matchers .notTo Async matchers .will .willNot .after(2)
  28. Coding Example Expecta - (void)populateView:(UIView *)view withData:(id)data { PersonView *personView

    = (PersonView *)view; PersonObject *personObject = (PersonObject *)data; personView.ageLabel.text = personObject.age.stringValue; } - (void)testPopulateViewWithDataDoesPopulateAgeLabelWithAge { UILabel *givenLabel = [[UILabel alloc] init]; OCMExpect(_view.ageLabel).andReturn(givenLabel); [_populator populateView:_view withData:_data]; expect(givenLabel.text).to.equal(_data.age.stringValue); }
  29. OCHamcrest personal favorite

  30. Coding Example OCHamcrest Object matchers conformsTo(@protocol(SomeProtocol)) equalTo(anObject) sameInstance(anObject) hasProperty(@"propertyName", @“propertyValue")

    instanceOf([SomeClass class]) Number matchers closeTo(@3) greaterThan(@3) greaterThanOrEqualTo(@3) lessThan(@3) lessThanOrEqualTo(@3) isFalse() isTrue()
  31. Coding Example OCHamcrest Text matchers containsSubstring(@"someString"); startsWith(@"someString"); endsWith(@"someString"); equalToIgnoringCase(@"someString"); Collection

    matchers contains(objectA, objectB) hasItems(objectA, objectB) everyItem() isEmpty() Logical matchers isNot() anything() allOf()
  32. Coding Example OCHamcrest - (void)populateView:(UIView *)view withData:(id)data { PersonView *personView

    = (PersonView *)view; PersonObject *personObject = (PersonObject *)data; personView.ageLabel.text = personObject.age.stringValue; } - (void)testPopulateViewWithDataDoesPopulateAgeLabelWithAge { UILabel *givenLabel = [[UILabel alloc] init]; [given(_view.ageLabel) willReturn:givenLabel]; [_populator populateView:_view withData:_data]; assertThat(givenLabel.text, is(equalTo(_data.age.stringValue)); }
  33. Specta

  34. Coding Example Specta SpecBegin(_PersonViewPopulator) describe(@"PersonViewPopulator", ^{ __block PersonViewPopulator *_populator; __block

    PersonObject *_data; __block PersonView *_view; __block id<ImageService> _imageService; __block NSURL *_imageURL; beforeEach(^{ _imageURL = [[NSURL alloc] initWithString:@"http://www.apple.com"]; _view = OCMClassMock([PersonView class]); _imageService = OCMProtocolMock(@protocol(ImageService)); _populator = [[PersonViewPopulator alloc] initWithImageService:_imageService]; _data = [[PersonObject alloc] initWithName:@"expectedName" imageURL:_imageURL slogan:@"expectedSlogan" location:@"expectedLocation" age:@25]; }); it(@"does invoke image service with expected fallback image", ^{ UIImageView *givenImageView = [[UIImageView alloc] init]; OCMExpect(_view.personImageView).andReturn(givenImageView); [_populator populateView:_view withData:_data]; OCMVerify([_imageService fetchImageFromURL:_data.imageURL fallBackImage:[UIImage personFallBackImage] forImageView:givenImageView]); }); }); SpecEnd
  35. Code coverage live demo

  36. UI Testing

  37. UI testing in Xcode live demo

  38. KIF live demo

  39. Clean Code - Robert C. Martin Test-Driven iOS Development -

    Graham Lee Test-Driven Development by Example - Kent Beck
  40. User feedback

  41. A/B testing

  42. None
  43. None
  44. B A

  45. Questions? Joris Dubois Consultant @ AppFoundry joris.dubois@appfoundry.be @DuboisJoris