Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing on iOS

AppFoundry
December 10, 2015

Testing on iOS

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

AppFoundry

December 10, 2015
Tweet

More Decks by AppFoundry

Other Decks in Technology

Transcript

  1. Your hosts
    Filip Maelbrancke
    Consultant @ AppFoundry
    [email protected]
    @fmaelbrancke
    Joris Dubois
    Consultant @ AppFoundry
    [email protected]
    @DuboisJoris

    View full-size slide

  2. AppFoundry
    appfoundry.be

    View full-size slide

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

    View full-size slide

  4. Coding Example
    DI: Spot the problem
    #import
    @interface UserDefaultManager : NSObject
    - (void)registerLeetInteger;
    @end
    #import "UserDefaultManager.h"
    @implementation UserDefaultManager
    - (void)registerLeetInteger {
    [[[NSUserDefaults alloc] initWithSuiteName:@"LeetAppGroup"] setInteger:1337 forKey:@"leet"];
    }
    @end

    View full-size slide

  5. Coding Example
    DI: Constructor Injection
    #import
    @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

    View full-size slide

  6. Coding Example
    DI: Method Injection
    #import
    @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

    View full-size slide

  7. Coding Example
    DI: Property Injection
    #import
    @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

    View full-size slide

  8. MASSIVE
    View Controller

    View full-size slide

  9. View Controller
    View Model
    MVVM
    View Controller
    View Model
    View Model

    View full-size slide

  10. Unit Testing
    frameworks & code coverage

    View full-size slide

  11. 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()

    View full-size slide

  12. 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);

    View full-size slide

  13. Coding Example
    OCMock: Verification
    OCMVerify([mock someMethod]);

    View full-size slide

  14. 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]);
    }

    View full-size slide

  15. OCMockito
    personal favorite

    View full-size slide

  16. 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));

    View full-size slide

  17. Coding Example
    OCMockito: Stubbing
    [given([mock someMethod]) willReturn:anObject];
    [given([mock aMethodReturningABoolean]) willReturnBool:YES];

    View full-size slide

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

    View full-size slide

  19. 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];
    }

    View full-size slide

  20. 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()

    View full-size slide

  21. 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)

    View full-size slide

  22. 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);
    }

    View full-size slide

  23. OCHamcrest
    personal favorite

    View full-size slide

  24. 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()

    View full-size slide

  25. 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()

    View full-size slide

  26. 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));
    }

    View full-size slide

  27. Coding Example
    Specta
    SpecBegin(_PersonViewPopulator)
    describe(@"PersonViewPopulator", ^{
    __block PersonViewPopulator *_populator;
    __block PersonObject *_data;
    __block PersonView *_view;
    __block id _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

    View full-size slide

  28. Code coverage
    live demo

    View full-size slide

  29. UI testing in Xcode
    live demo

    View full-size slide

  30. KIF
    live demo

    View full-size slide

  31. Clean Code - Robert C. Martin
    Test-Driven iOS Development - Graham Lee
    Test-Driven Development by Example - Kent Beck

    View full-size slide

  32. User feedback

    View full-size slide

  33. Questions?
    Joris Dubois
    Consultant @ AppFoundry
    [email protected]
    @DuboisJoris

    View full-size slide