Post on 12-Nov-2014
description
Unit Testing with iOS
Have you ever developed in...
• Ruby on Rails• PHP on Symfony• PHP on Zend• Python on Django• Java on J2EE
All of these languages / frameworks make it easy to write/run:
• Unit tests• Functional tests• Integration tests
Unfortunately...
• "Objective-C on Cocoa Touch" didn't make the list.
Why test in the first place?
• You can't write a test case until you know what "good output" and "bad output" look like: writing a test case forces you to define the problem.
• And, if you write test cases before you write production code, you are not emotionally attached to an implementation -- if you discover a mistake, you're likely to address it.
Unit testing in Objective-C
• Functional tests are a bit painful yet (I feel) in Objective-C / Cocoa Touch - you have to write Javascript commands
• Let's focus on unit testing only today (larger "units" can start to act as functional tests).
Unit testing frameworks provide...
• Setup/teardown helpers run before each test• Assertion macros like:
o STAssertTrue(NO,@"I am a complete failure");o STAssertEqual(foo,bar,@"foo should = bar");
• A test runner than runs all of, or a subset of, your unit test cases & reports the results.
Choosing a unit testing framework...• You have 2 choices:
o GHUnit (on GitHub)o OCUnit (built into Xcode)
• I spent a long time1 considering the pros and cons of each, but if you are:o Writing a new projecto Won't have hundreds of test caseso Are using Xcode 4
... then I argue you should probably choose OCUnit.
1 Approximately 10 hours on a transpacific flight
More details on OCUnit...
• When you tick "Use Unit Testing" on the New Project template wizard, the set up is already done
• ⌘U is all you need to press to run your tests• Does not have the -setUpClass and -tearDownClass helpers
• Does not allow you to run unit tests individually, must run entire test suite (some MAY argue that this is a "feature")
More details on GHUnit...
• Handier macros & test class helper methods than OCUnit
• Can run a single test (good for debugging if you have many tests)
• Much sexier log output than OCUnit• Not built into Xcode 4, you have to set it up as a
separate target and build & link its framework• More time-consuming to run tests• Runs in the Simulator or on the device as an actual
target, with its own UI
Whichever you choose...
• They will both get the job done, and they are both so much better than having no unit tests at all.
• OK, so all set up? If you need help on GHUnit, see my blog post on the subject:
http://longweekendmobile.com/2011/02/23/tdd-best-practices-testing-in-ios4-with-ghunit-part-1/
Let's write some tests...
There are 2 times to write unit tests:• Before developing a new feature (wow, you TDD
stud)• When a section of code is:
o Buggy - doesn't work how you expecto Fragile - seems to break all the timeo Coupled - seems to break all the time when you
change things elsewhere, and in non-obvious ways*
* Usually this type of test writing is done after a bug by such code is found, but also can be done before a major refactor. Or both. Unit tests also allow you to refactor mercilessly, which is a good habit to be in.
What does a unit test look like?
// MyTest.m (I roll .h into this)
- (void) setUp {}
- (void) tearDown {}
- (void) testSomething {}
- (void) testAnotherThing {}
I use setUp and tearDown for this...@interface LWEFormViewTest { LWEFormView _testObject;}
- (void) setUp { _testObject = [[LWEFormView alloc] initWithFrame:CGRectZero];}
- (void) tearDown { [_testObject release]; _testObject = nil;}
...which helps me focus on tests:
- (void) testTextFieldAdd{ UITextField *aField = [[UITextField alloc] init]; aField.tag = 1; [_testObject addSubview:aField];
int numForms = [[_testObject formOrder] count];
// Assertions go here
[aField release];}
Assertion macros finish off the test
- (void) testTextFieldAdd{ UITextField *aField = [[UITextField alloc] init]; aField.tag = 1; [_testObject addSubview:aField];
int numForms = [[_testObject formOrder] count];
GHAssertTrue((numForms == 1), @"We added an object, why wasn't it showing up?");
id<NSObject> lastObj = [[_testObject formOrder] lastObject]; GHAssertEqualObjects(lastObj,aField, @"Why did a different one come back?");
[aField release];}
GHUnit & OCUnit macros
GHUnit: • GHAssertTrue• GHAssertEqualObjects• ... many more
OCUnit:• STAssertTrue• STAssertEquals• ... many more
Unit tests you should always write
• Incorrect object types• Out-of-bounds values• Boundary values• Expected values• Using loops to stress test... but maybe not unit
Q+A
• Interested to hear everyone's experiences with testing on iOS