Slide 1

Slide 1 text

Automated Testing (Cocoaheads Montréal - 11/02/2014)

Slide 2

Slide 2 text

Who ? ▸ Romain Pouclet ▸ iOS developer ▸ Building stuff at TechSolCom

Slide 3

Slide 3 text

How many developer in the room?

Slide 4

Slide 4 text

How many of you have already written a unit test case?

Slide 5

Slide 5 text

How many of you have already written a functional test case ?

Slide 6

Slide 6 text

The streching session is over, you can go back to your beer

Slide 7

Slide 7 text

Reasons to test

Slide 8

Slide 8 text

Reasons to test ▸ Make sure everything work as expected ▸ Avoid regression ▸ Natural documentation ▸ Ease maintenance and refactoring ▸ Sleep better at night

Slide 9

Slide 9 text

Reasons not to test

Slide 10

Slide 10 text

Reasons not to test Prototyping Deadlines Testing is hard I don't need them, I know what I'm doing

Slide 11

Slide 11 text

Reasons not to test Prototyping Deadlines Testing is hard I don't need them, I know what I'm doing

Slide 12

Slide 12 text

Reasons not to test Prototyping Deadlines Testing is hard I don't need them, I know what I'm doing

Slide 13

Slide 13 text

Reasons not to test Prototyping Deadlines Testing is hard I don't need them, I know what I'm doing

Slide 14

Slide 14 text

Example Parsing some XML

Slide 15

Slide 15 text

Example : XML parsing ▸ Augmented reality application using Vuforia ▸ Metadata stored here (Vuforia) and there (Client database)

Slide 16

Slide 16 text

Example : XML parsing 1 Harry Potter and the goblet of fire 2 Harry Potter and the Deathly Hallows

Slide 17

Slide 17 text

Dead simple XML parsing PCSGetBookDetailsParserDelegate *parsingDelegate = [[PCSGetBookDetailsParserDelegate alloc] init]; NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL: url]; parser.delegate = parsingDelegate; BOOL result = [parser parse]; PCSBook *book = parsingDelegate.book;

Slide 18

Slide 18 text

The developer (not me, another nice guy) didn't write any test for this

Slide 19

Slide 19 text

"There are too much metadata on Vuforia, please only use item and image data and fetch the rest from the backoffice"

Slide 20

Slide 20 text

Sure! [[PCSGetBookDetailsParserDelegate alloc] initWithWhiteList: @[@"imagedata", @"item"]];

Slide 21

Slide 21 text

Guess what?

Slide 22

Slide 22 text

There is a bug in the application when the user search for a book on a tuesday — A (nice) client

Slide 23

Slide 23 text

Achievement unlocked ! humility

Slide 24

Slide 24 text

Write a failing test case - (void)testItemIdIsNotOverridenByRecommandationItemId { NSURL *url = [[NSBundle bundleForClass: [self class]] URLForResource: @"342279" withExtension: @"xml"]; NSAssert(url, @"URL for sample payload should be properly loadded"); PCSGetBookDetailsParserDelegate *parsingDelegate = [[PCSGetBookDetailsParserDelegate alloc] initWithWhiteList: @[@"imagedata", @"item"]]; NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL: url]; parser.delegate = parsingDelegate; BOOL result = [parser parse]; NSAssert(result, @"Payload shoud be successfully parsed."); PCSBook *book = parsingDelegate.book; XCTAssertEqualObjects(@"342279", book.item); }

Slide 25

Slide 25 text

Failing test case : load data you know NSURL *url = [[NSBundle bundleForClass: [self class]] URLForResource: @"342279" withExtension: @"xml"]; NSAssert(url, @"URL for sample payload should be properly loadded");

Slide 26

Slide 26 text

Failing test case : do the actual parsing PCSGetBookDetailsParserDelegate *parsingDelegate = [[PCSGetBookDetailsParserDelegate alloc] initWithWhiteList: @[@"imagedata", @"item"]]; NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL: url]; parser.delegate = parsingDelegate; BOOL result = [parser parse]; NSAssert(result, @"Payload shoud be successfully parsed.");

Slide 27

Slide 27 text

Failing test case : add your assertions PCSBook *book = parsingDelegate.book; XCTAssertEqualObjects(@"342279", book.item);

Slide 28

Slide 28 text

Run your code ⌘ + U or xcodebuild test

Slide 29

Slide 29 text

Or... Xctool (facebook) $ brew install xctool $ xctool -project XMLParsing.xcodeproj -scheme XMLParsing test

Slide 30

Slide 30 text

Or... Xcodebuild and XCPretty $ gem install xcpretty $ xcodebuild test | xcpretty

Slide 31

Slide 31 text

The secret of happiness* 1. Find a bug 2. Write a (failing) test case 3. Fix the bug 4. Repeat (* not really)

Slide 32

Slide 32 text

Unit testing Test the smallest part of an application (= unit)

Slide 33

Slide 33 text

A good test suite is...

Slide 34

Slide 34 text

Easy to read - (void)testSomethingShouldHappenThisWay { Stuff *someStuff = [[Stuff alloc] init]; XCTAssertEqualObjects(@"Expected result", [someStuff performSomeAction], @"The action should do something") }

Slide 35

Slide 35 text

Fast to run

Slide 36

Slide 36 text

Separation of concern Don't duplicate your checks : higher-level tests assume that lower-level code works.

Slide 37

Slide 37 text

Avoid singletons @implementation PCSSuperImportantObject - (instancetype)init { self = [super init]; if (self) { self.connection = [PCSTwitterConnection sharedConnection]; } return self; } @end

Slide 38

Slide 38 text

Use dependency injection! @implementation PCSSuperImportantObject - (instancetype)initWithConnection:(PCSTwitterConnection *)connection { self = [super init]; if (self) { self.connection = connection; } return self; } @end

Slide 39

Slide 39 text

... So you can mock them later id connection = [OCMockObject mockForClass: [PCSTwitterConnection class]]; [[[connection stub] andReturn: @367] followerCountsForUsername: [OCMArg any]]; NSLog(@"Followers = %@", [connection followerCountsForUsername: @"CocoaheadsMTL"]);

Slide 40

Slide 40 text

Don't test everything HSWizard *harry = [[HSWizard alloc] init]; harry.house = @"Gryffindor"; XCTAssertEqualsObject(@"Gryffindor", harry.house);

Slide 41

Slide 41 text

But wait there's more !

Slide 42

Slide 42 text

Acceptance testing using KIF - (void)testSuccessfulLogin { [tester enterText: @"https://confluence-url.com" intoViewWithAccessibilityLabel: @"Confluence URL"]; [tester enterText: @"apple.review" intoViewWithAccessibilityLabel: @"Confluence username"]; [tester enterText: @"applereview" intoViewWithAccessibilityLabel: @"Confluence password"]; [tester tapViewWithAccessibilityLabel: @"Log In"]; // Verify that the login succeeded [tester waitForTappableViewWithAccessibilityLabel: @"Spaces list"]; [tester tapRowInTableViewWithAccessibilityLabel: @"Spaces list" atIndexPath: [NSIndexPath indexPathForRow: 0 inSection: 0]]; [tester waitForTappableViewWithAccessibilityLabel: @"Main menu"]; }

Slide 43

Slide 43 text

Acceptance testing using Frank Feature: As a user I want to be able to see the weather of a region So I can see if I need to bring flip flops or a snow shovel Scenario: I need to be able to select a city Given I launch the app And I touch "Select weather city" And I wait to see "Alma" And I touch "Alma" Then I should see "Alma" $ cucumber features/weather.feature

Slide 44

Slide 44 text

It is not about testing everything It is about raising your level of confidence in your code

Slide 45

Slide 45 text

Thank you! (Questions ?)