A presentation by Joris Dubois (AppFoundry) for the December 2015 Mobel user group meetup
Your hostsFilip MaelbranckeConsultant @ AppFoundryfi[email protected]@fmaelbranckeJoris DuboisConsultant @ AppFoundry[email protected]@DuboisJoris
View Slide
AppFoundryappfoundry.be
Patterns
Dependency Injectiona 25-dollar term for a 5-cent concept
Coding ExampleDI: 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
Coding ExampleDI: 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
Coding ExampleDI: 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
Coding ExampleDI: 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
MVVM
MVC
MASSIVEView Controller
View ControllerView ModelMVVMView ControllerView ModelView Model
Unit Testingframeworks & code coverage
OCMock
Coding ExampleOCMock: MockingClass mock OCMClassMock([SomeClass class]);Protocol mock OCMProtocolMock(@protocol(SomeProtocol));Strict class & protocolmockOCMStrictClassMock([SomeObject class]);OCMStrictProtocolMock(@protocol(SomeProtocol));Partial mock OCMPartialMock(anObject);Observer mock OCMObserverMock()
Coding ExampleOCMock: StubbingOCMStub([mock someMethod]).andReturn(anObject);OCMStub([mock someMethodReturningABool]).andReturn(YES);OCMStub([mock someMethod]).andCall(anotherObject, @selector(aDifferentMethod));OCMStub([mock someMethod]).andThrow(anException);
Coding ExampleOCMock: VerificationOCMVerify([mock someMethod]);
Coding ExampleOCMock- (void)populateView:(UIView *)view withData:(id)data {PersonView *personView = (PersonView *)view;PersonObject *personObject = (PersonObject *)data;[_imageService fetchImageFromURL:personObject.imageURL fallBackImage:[UIImagepersonFallBackImage] 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]);}
OCMockitopersonal favorite
Coding ExampleOCMockito: MockingClass mock mockClass([SomeClass class]);Protocol mockmockProtocol(@protocol(SomeProtocol));mockProtocolWithoutOptionals(@protocol(SomeProtocol));Class & protocol mockmockObjectAndProtocol([SomeObject class],@protocol(SomeProtocol));
Coding ExampleOCMockito: Stubbing[given([mock someMethod]) willReturn:anObject];[given([mock aMethodReturningABoolean]) willReturnBool:YES];
Coding ExampleOCMockito: Verification[verify(mock) someMethod];[verifyCount(mock, times(1)) someMethod];[verifyCount(mock, never()) someMethod];
Coding ExampleOCMockito- (void)populateView:(UIView *)view withData:(id)data {PersonView *personView = (PersonView *)view;PersonObject *personObject = (PersonObject *)data;[_imageService fetchImageFromURL:personObject.imageURL fallBackImage:[UIImagepersonFallBackImage] 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];}
Expecta
Coding ExampleExpectaObject 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()
Coding ExampleExpectaText matchers.beginWith(@"someString").endWith(@"someString").match(@"regex")Collection matchers.contain(objectA, objectB).beSupersetOf(objectA, objectB).haveCountOf().beEmpty()Logical matchers .notToAsync matchers.will.willNot.after(2)
Coding ExampleExpecta- (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);}
OCHamcrestpersonal favorite
Coding ExampleOCHamcrestObject matchersconformsTo(@protocol(SomeProtocol))equalTo(anObject)sameInstance(anObject)hasProperty(@"propertyName", @“propertyValue")instanceOf([SomeClass class])Number matcherscloseTo(@3)greaterThan(@3)greaterThanOrEqualTo(@3)lessThan(@3)lessThanOrEqualTo(@3)isFalse()isTrue()
Coding ExampleOCHamcrestText matcherscontainsSubstring(@"someString");startsWith(@"someString");endsWith(@"someString");equalToIgnoringCase(@"someString");Collection matcherscontains(objectA, objectB)hasItems(objectA, objectB)everyItem()isEmpty()Logical matchersisNot()anything()allOf()
Coding ExampleOCHamcrest- (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));}
Specta
Coding ExampleSpectaSpecBegin(_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:_imageURLslogan:@"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:[UIImagepersonFallBackImage] forImageView:givenImageView]);});});SpecEnd
Code coveragelive demo
UI Testing
UI testing in Xcodelive demo
KIFlive demo
Clean Code - Robert C. MartinTest-Driven iOS Development - Graham LeeTest-Driven Development by Example - Kent Beck
User feedback
A/B testing
BA
Questions?Joris DuboisConsultant @ AppFoundry[email protected]@DuboisJoris