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
290
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
110
Android In Practice
appfoundrybe
0
140
Android Introduction 3.0 by Siebe
appfoundrybe
0
120
Android in Practice (long)
appfoundrybe
0
210
React Native - cross-platform mobile app development
appfoundrybe
0
190
React Native Storybook
appfoundrybe
0
480
the ionic crash course
appfoundrybe
1
190
View based apps with Conductor
appfoundrybe
0
650
Android Accessibility at GDG Devfest Brussels 2016
appfoundrybe
0
650
Other Decks in Technology
See All in Technology
Agile Leadership Summit Keynote 2026
m_seki
1
570
Azure Durable Functions で作った NL2SQL Agent の精度向上に取り組んだ話/jat08
thara0402
0
160
Webhook best practices for rock solid and resilient deployments
glaforge
1
280
学生・新卒・ジュニアから目指すSRE
hiroyaonoe
2
580
Sansan Engineering Unit 紹介資料
sansan33
PRO
1
3.8k
AWS Network Firewall Proxyを触ってみた
nagisa53
1
210
~Everything as Codeを諦めない~ 後からCDK
mu7889yoon
3
310
プロダクト成長を支える開発基盤とスケールに伴う課題
yuu26
4
1.3k
Amazon Bedrock Knowledge Basesチャンキング解説!
aoinoguchi
0
130
外部キー制約の知っておいて欲しいこと - RDBMSを正しく使うために必要なこと / FOREIGN KEY Night
soudai
PRO
12
5.2k
配列に見る bash と zsh の違い
kazzpapa3
1
130
生成AIを活用した音声文字起こしシステムの2つの構築パターンについて
miu_crescent
PRO
2
170
Featured
See All Featured
Code Reviewing Like a Champion
maltzj
527
40k
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
0
2.3k
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
120
The Limits of Empathy - UXLibs8
cassininazir
1
210
Making the Leap to Tech Lead
cromwellryan
135
9.7k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.7k
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.1k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
2.7k
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.3k
How to build a perfect <img>
jonoalderson
1
4.9k
GraphQLの誤解/rethinking-graphql
sonatard
74
11k
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
130
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