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
AppFoundry
December 10, 2015
Technology
0
250
Testing on iOS
A presentation by Joris Dubois (AppFoundry) for the December 2015 Mobel user group meetup
AppFoundry
December 10, 2015
Tweet
Share
More Decks by AppFoundry
See All by AppFoundry
Introductie iOS - Jens
appfoundrybe
0
92
Android In Practice
appfoundrybe
0
130
Android Introduction 3.0 by Siebe
appfoundrybe
0
110
Android in Practice (long)
appfoundrybe
0
200
React Native - cross-platform mobile app development
appfoundrybe
0
160
React Native Storybook
appfoundrybe
0
420
the ionic crash course
appfoundrybe
1
180
View based apps with Conductor
appfoundrybe
0
600
Android Accessibility at GDG Devfest Brussels 2016
appfoundrybe
0
520
Other Decks in Technology
See All in Technology
Introduction to OpenSearch Project - Search Engineering Tech Talk 2025 Winter
tkykenmt
2
270
ABWG2024採択者が語るエンジニアとしての自分自身の見つけ方〜発信して、つながって、世界を広げていく〜
maimyyym
1
230
OCI Success Journey OCIの何が評価されてる?疑問に答える事例セミナー(2025年2月実施)
oracle4engineer
PRO
2
280
Postman AI Agent Builderで AI Agentic workflow のプロトタイピング / Prototyping AI Agentic Workflow with Postman AI Agent Builder
yokawasa
0
180
MLflowはどのようにLLMOpsの課題を解決するのか
taka_aki
0
170
フォーイット_エンジニア向け会社紹介資料_Forit_Company_Profile.pdf
forit_tech
1
1.7k
Platform Engineeringで クラウドの「楽しくない」を解消しよう
jacopen
4
290
困難を「一般解」で解く
fujiwara3
9
2.9k
Amazon Q Developerの無料利用枠を使い倒してHello worldを表示させよう!
nrinetcom
PRO
2
130
30→150人のエンジニア組織拡大に伴うアジャイル文化を醸成する役割と取り組みの変化
nagata03
0
410
開発者体験を定量的に把握する手法と活用事例
ham0215
0
150
アウトカムを最大化させるプロダクトエンジニアの動き
hacomono
PRO
0
120
Featured
See All Featured
Building an army of robots
kneath
303
45k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
28
1.9k
Git: the NoSQL Database
bkeepers
PRO
429
65k
How to Ace a Technical Interview
jacobian
276
23k
Rebuilding a faster, lazier Slack
samanthasiow
80
8.9k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
11
560
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
115
51k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
21
2.5k
GraphQLの誤解/rethinking-graphql
sonatard
69
10k
The Straight Up "How To Draw Better" Workshop
denniskardys
232
140k
Fontdeck: Realign not Redesign
paulrobertlloyd
83
5.4k
YesSQL, Process and Tooling at Scale
rocio
172
14k
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