Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Testing on iOS
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
AppFoundry
December 10, 2015
Technology
320
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Testing on iOS
A presentation by Joris Dubois (AppFoundry) for the December 2015 Mobel user group meetup
AppFoundry
December 10, 2015
More Decks by AppFoundry
See All by AppFoundry
Introductie iOS - Jens
appfoundrybe
0
120
Android In Practice
appfoundrybe
0
160
Android Introduction 3.0 by Siebe
appfoundrybe
0
150
Android in Practice (long)
appfoundrybe
0
240
React Native - cross-platform mobile app development
appfoundrybe
0
210
React Native Storybook
appfoundrybe
0
500
the ionic crash course
appfoundrybe
1
210
View based apps with Conductor
appfoundrybe
0
670
Android Accessibility at GDG Devfest Brussels 2016
appfoundrybe
0
690
Other Decks in Technology
See All in Technology
35分でわかるEffective Platform Engineering
nwiizo
5
550
2027年のMetricKit
kantacky
0
130
AI-DLC実践録_フルサイクル開発への挑戦
miyuc
0
320
属人化を叩き割れ!「制作加速」と「安定化」の矛盾を打破する:8年目プロダクトが辿り着いた「ミスが起こり得ない」アセット制作フローの全貌
gree_tech
PRO
0
120
コーチングの奥義 何もしないテクニック
jinwatanabe
0
140
【GCC2026】TrueHDRIを用いたルックデブ環境とライティングテクニック
bandainamcostudios
PRO
0
130
What's new in Go 1.27?
ciarana
0
280
カートの信頼性を担保するWireMockを使ったe2eテスト
ykagano
0
620
Sansan Engineering Unit 紹介資料
sansan33
PRO
1
5k
8bit CPU 2026
koba789
1
340
Model Studio CLI × Token Plan
maigo999
0
200
tamachi.go 誕生の裏側
rymiyamoto
1
190
Featured
See All Featured
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.9k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6k
GraphQLとの向き合い方2022年版
quramy
50
15k
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
290
BBQ
matthewcrist
89
10k
First, design no harm
axbom
PRO
2
1.3k
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
290
Fireside Chat
paigeccino
42
4k
Typedesign – Prime Four
hannesfritz
42
3.1k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.8k
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
250
Transcript
Your hosts Filip Maelbrancke Consultant @ AppFoundry fi
[email protected]
@fmaelbrancke Joris
Dubois Consultant @ AppFoundry
[email protected]
@DuboisJoris
AppFoundry appfoundry.be
None
Patterns
Dependency Injection a 25-dollar term for a 5-cent concept
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
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
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
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
MVVM
MVC
MASSIVE View Controller
View Controller View Model MVVM View Controller View Model View
Model
Unit Testing frameworks & code coverage
OCMock
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()
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);
Coding Example OCMock: Verification OCMVerify([mock someMethod]);
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]); }
OCMockito personal favorite
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));
Coding Example OCMockito: Stubbing [given([mock someMethod]) willReturn:anObject]; [given([mock aMethodReturningABoolean]) willReturnBool:YES];
Coding Example OCMockito: Verification [verify(mock) someMethod]; [verifyCount(mock, times(1)) someMethod]; [verifyCount(mock,
never()) someMethod];
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]; }
Expecta
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()
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)
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); }
OCHamcrest personal favorite
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()
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()
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)); }
Specta
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
Code coverage live demo
UI Testing
UI testing in Xcode live demo
KIF live demo
Clean Code - Robert C. Martin Test-Driven iOS Development -
Graham Lee Test-Driven Development by Example - Kent Beck
User feedback
A/B testing
None
None
B A
Questions? Joris Dubois Consultant @ AppFoundry
[email protected]
@DuboisJoris