Upload
tim-duckett
View
523
Download
0
Embed Size (px)
Citation preview
The Cowardly Test-o-phobe’s
Guide To iOS Testing
Why is testing scary?
• Because you’re not a computer scientist
• Because Java bores you rigid
• Because there’s nothing in life more tedious thana “thought leader”
• Because you get paid to ship apps
Why is testing important?
• You’ll write code with fewer bugs in it
• You’ll introduce fewer bugs as the project develops
• You’ll document the project without writing any docs
• You’ll knit yourself a security blanket
In the next 40 mins…
• I’ll try to convince you that it’s not all *that* scary
• I’ll show you how to get started, even on an existing project
• I’ll show you some tools you can start using on Monday
The basics
• Does my code do what it’s supposed to do?
• Does my code cope with unexpected values?
• Does my code break when I change things?
Test first
• Write a test to describe what you want your code to do.
• Run the test and watch it fail.
• Write the code you need to get the test to pass.
• Rinse and repeat.
Why not test last?
• Because there’s no motivation to test once it runs
• Because there’s never time
• Because you’re confirming your own prejudices
Test first
• Write a failing test - RED
• Write the code to make it pass - GREEN
• Write the next test
• Write the code to make it - and all the previous tests - pass REFACTOR
Challenges of testing iOS
• Unit testing is designed around testing code
• iOS apps are largely interface driven
• How do you test the effect of taps, touches, swipes and pinches?
The tools
• You’ll need a test framework
• Two basic styles:
• JUnit
• RSpec
• Xcode ships with XCTest, which is a JUnit-style
Which one?
• Kiwi - a personal choice, but I dislike the JUnit syntax
• Kiwi also includes a nice mocking framework, of which more later
• Other alternatives are available
• Bottom line: use what you feel most comfortable with
UI testing approaches• “Robot fingers”
• Borrowed from the web world
• Peers inside the view hierarchy and checks what’s going on
• It works, but it’s fiddly to set up, fiddly to use, and SLOW
UI testing approaches• Testing with code
• UI interactions are passed down into code via IBAction methods
• The IBAction methods are our code, so let’s test that to make sure everything works.
• Works on the basis there’s no point in testing other people’s code (especially Apple’s!)
UI testing approaches
The approach
• Instantiate your view controller
• Recreate your view hierarchy
• Manipulate and test your views
• Err…
• That’s it.
Dependencies• One of the biggest problems in getting started with
testing an app is how to handle dependencies
• Classes and methods seldom exist in isolation from each other
• What happens if you are reliant on external data sources such as network APIs?
• How do you test your code without nailing up all the supporting objects?
Mocking and stubbing
• How to do it
Mocking
NOT DANIEL CRAIG
Mocking
• Mocking is the process of creating “stunt doubles”
• The mock object stands in for the real thing
• Also known as “duck typing”
• If it looks like a duck, and swims like a duck, and quacks like a duck… it probably is a duck.
Stubbing
• Stubbing takes an existing object, and returns a value that you provide
• You can stub mocks that you’ve created
• You can also stub real objects to return canned values
Mocking• Creating mock objects:
id myMockedSubclass = [MyClass mock];
• Stubbing methods without return values: ! [myMockedSubclass stub:@selector(stubbedMethod)]; ! [myMockedSubclass stubbedMethod];
• Stubbing methods with return values: ! [myMockedSubclass stub:@selector(myMethod) andReturn:@"the return value”]; ! NSString *returnValue = [myMockedSubclass myMethod];
Mocking your objects
id theEnterprise = [Starship mock]; [theEnterprise stub:@selector(warpFactor) andReturn:@9]; ... [communicator report:[theEnterprise warpFactor]];
Mocking “real” objects
id mockDefaults = [NSUserDefaults mock]; [[mockDefaults stubAndReturn:@10] valueForKey:@"userId"]; [[[mockDefaults valueForKey:@"userId"] should] equal:@10]; ... NSNumber *userId = [mockDefaults objectForKey:@"userId"];
Network testing
Testing networks
• What do you do if your tests imply a dependency on a network data source?
• How do you handle variations in responses?
• How do you handle latency?
• How do you deal with throttling and rate limits?
Approaches
• Set up a “stunt double” API using something like Node or Sinatra
• Stub network calls and return “canned” values from within your tests
OHHTTPStubs
• Returns “canned” values in response to network calls, e.g. from files that you embed in the project
• Can simulate different types of network speed
• Can simulate slow API responses so that you can test how to handle progress indicators or timeouts
Capturing the data
• Grab the data using wget and save it into a file
wget “http://site.com/page” -O response.json
• Add the response file to your project bundle
• Serve the response file with OHHTTPStubs
Mocking a response[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { Examine the URL components: - baseURL - path - relativePath - parameterString etc etc etc Return TRUE when matched } withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) { Build and return an OHHTTPStubsResponse object: - load a file from the local filesystem - send back a specific HTTP status code - send back custom headers - send back errors - adjust response and request lead times }];
Capturing the data[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { return [request.URL.path isEqualToString:@"/v1/connections"]; } withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) { return [[OHHTTPStubsResponse responseWithFileAtPath:OHPathForFileInBundle(@"v1_con.json", nil) statusCode:200 headers:@{@"Content-Type":@"text/json"}] requestTime:4.0f responseTime:1.0f]; !}];
and finally…
• Test your user interfaces in code!
• Mock and stub your classes to handle internal dependencies!
• Fake network connections!
• Testing doesn’t have to be scary! , ,
I am
adoptioncurve.net
Twitter, GitHub et al @timd
!
We arecentralway.comand are hiring!