iOS testing

Preview:

DESCRIPTION

Testing in iOS using Kiwi and OCMock frameworks

Citation preview

Testing in iOS10.01.2013 by Tomasz Janeczko

About me

Tomasz Janeczko

• iOS developer in Kainos

• Enthusiast of business, electronics, Rails & Heroku

• Organizer of first App Camp in UK and PL

So let’s talk about testing.

Why we test?

So?

• Reliability

• Regression

• Confidence (e.g. refactoring)

Why not to test?

Why not to test?

• Heavy dependence on UI

• Non-testable code

• Bad framework

How to address issues

• Sample - downloading stuff from interwebz

First fault

Writing tests after writing code

Separation of concerns

Separation of concerns

• Let’s separate out the UI code

• Same for services interaction

Demo of tests

Writing testsMeet Kiwi and OCMock

Kiwi

• RSpec-like tests writing

• Matchers

• Cleaner and more self-descriptive code

Kiwi

describe(@"Tested class", ^{

context(@"When created", ^{

it(@"should not fail", ^{ [[theValue(0) should] equal:theValue(0)]; });

});

});

Kiwi

describe(@"Tested class", ^{

context(@"When created", ^{

it(@"should not fail", ^{ id viewController = [ViewController new]; [[viewController should] conformToProtocol:@protocol(UITableViewDelegate)]; });

});

});

Matchers[subject  shouldNotBeNil]

• [subject  shouldBeNil]

• [[subject  should]  beIdenticalTo:(id)anObject] - compares id's

• [[subject  should]  equal:(id)anObject]

• [[subject  should]  equal:(double)aValue  withDelta:(double)aDelta]

• [[subject  should]  beWithin:(id)aDistance  of:(id)aValue]

• [[subject  should]  beLessThan:(id)aValue]

• etc.  etc.

Compare to SenTesting Kit

[[subject  should]  equal:anObject]

compare  with

STAssertEquals(subject,  anObject,  @”Should  be  equal”);

OCMock

• Mocking and stubbing library for iOS

• Quite versatile

• Makes use of NSProxy magic

Sample workflows

Classic calculator sample

describe(@"Calculator",  ^{                context(@"with  the  numbers  60  and  5  entered",  ^{                RPNCalculator  *calculator  =  [[RPNCalculator  alloc]  init];                                beforeEach(^{                        [calculator  enter:60];                        [calculator  enter:5];                });

               afterEach(^{  

                       [calculator  clear];                });                              it(@"returns  65  as  the  sum",  ^{                        [[theValue([calculator  add])  should]  equal:65  withDelta:.01];                });

Test if calls dep methods

1. Create a mock dependency

2. Inject it

3. Call the method

4. Verify

Test dependency

// Create the tested object and mock to exchange part of the functionalityviewController = [ViewController new];mockController = [OCMockObject partialMockForObject:viewController];

// Create the mock and change implementation to return our classid serviceMock = [OCMockObject mockForClass:[InterwebzService class]];[[[mockController stub] andReturn:serviceMock] service]; // Define expectations[[serviceMock expect] downloadTweetsJSONWithSuccessBlock:[OCMArg any]]; // Run the tested method[viewController tweetsButtonTapped:nil]; // Verify - throws exception on failure[mockController verify];

Testing one layer

• Isolate dependencies

• Objective-C is highly dynamic - we can change implementations of private methods or static methods

• We can avoid IoC containers for testing

Accessing private methods

Accessing private methods

@interface ViewController()

- (void)startDownloadingTweets;

@end

...[[mockController expect] startDownloadingTweets];

Static method testing

• Through separation to a method@interface ViewController()

- (NSUserDefaults *)userDefaults;

@end

...

id mockDefaults = [OCMockObject mockForClass:[NSUserDefaults class]];

[[[mockDefaults expect] andReturn:@"Setting"] valueForKey:[OCMArg any]];

[[[mockController stub] andReturn:mockDefaults] userDefaults];

Static method testing

• Through method swizzlingvoid  SwizzleClassMethod(Class  c,  SEL  orig,  SEL  new)  {

       Method  origMethod  =  class_getClassMethod(c,  orig);        Method  newMethod  =  class_getClassMethod(c,  new);

       c  =  object_getClass((id)c);

       if(class_addMethod(c,  orig,  method_getImplementation(newMethod),  method_getTypeEncoding(newMethod)))                class_replaceMethod(c,  new,  method_getImplementation(origMethod),  method_getTypeEncoding(origMethod));        else                method_exchangeImplementations(origMethod,  newMethod);}

Normal conditions applydespite it’s iOS & Objective--C

Problems of „mobile devs”

• Pushing code with failing tests

• Lot’s of hacking together

• Weak knowledge of VCS tools - merge nightmares

Ending thoughts

• Think first (twice), then code :)

• Tests should come first

• Write the failing test, pass the test, refactor

• Adequate tools can enhance your testing experience

Ending thoughts

• Practice!

Thanks!

Questions