From: http://longweekendmobile.com/2011/02/23/tdd-best-practices-testing-in-ios4-with-ghunit-part-1/
EDIT: This post is still relevant and useful for installing/setting up unit tests on iOS with GHUnit. However, as of Xcode 4, we have also begun using OCUnit again. See the article discussing which one is right for you.
Most Web frameworks (RoR, symfony, etc.) support test-driven development (TDD), and Long Weekend absolutely drinks the Kool-Aid (did you know it was actually Flavor Aid?).
Then we became iOS developers… right. “Let’s do the time warp again.”
Effective testing at both the unit and functional level is key to writing good code: tests enable you to refactor mercilessly, catch regressions almost immediately, and for the regressions you don’t catch, you write new tests so they never come back again. Liquid confidence.
We’re starting to get our TDD mojo back on iOS as well, with these 2 tools:
- GHUnit – a TDD-friendly, debug-able unit testing framework that runs in real time on the device & simulator
- UI Automation with Instruments – an Apple-provided (since iOS SDK 4.0) framework for functional integration testing that runs in real time on the device & simulator
In this part 1, we’ll cover GHUnit. Part 2, to be released later, will cover UI Automation.
How-To: effective iOS unit testing with GHUnit
GHUnit was born from GTM’s (Google Toolkit for Mac) unit testing, which itself was already a much better step forward than Apple’s own XCode-provided OCTest framework (based on SenTest). GHUnit is a breeze to work with once you get it set up, but there are some non-trivial steps.
1. Building the GHUnit Framework
I’ll assume that you are using git as your source version control software.
GHUnit’s documentation isn’t bad, but they list “Installing” prior to “Building”. You have to build the library before you can install it into your project = a gotcha. Let’s do that.
First, clone a copy of the GHUnit repository from github (as a submodule would be smart). We usually put our external libraries in a separate directory as not to get confused:
> cd /your/project/directory
> mkdir external-libs
> git submodule add git://github.com/gabriel/gh-unit.git external-libs/gh-unit
Unfortunately, as of this writing, the authors of GHUnit haven’t updated their project file to the latest Xcode SDK settings, so you have to change their project file to allow it to build (if you have iOS SDK 4.2+).
- Use XCode to open the GHUnitIPhone.xcodeproj project file in the Project-IPhone subdirectory.
- Your “Base SDK” will probably be missing.
- Navigate to Project -> Edit Project Settings menu.
- Change “Base SDK for all configurations” to “Latest iOS (currently set to x.x)”. In my current case, x.x is 4.2.
- Close the project file and re-open it.
- XCode should now play nice and tell you it knows how to build this project.
But not so fast, GHUnit likes to be built from the command line, per this page in their documentation. Fire up a Terminal window and navigate to your gh-unit/Project-IPhone subdirectory. Make the sucker:
> make
The Makefile will churn for a bit, and you’ll eventually see the end of the script:
** BUILD SUCCEEDED **
2. Installing GHUnit into Your Project
So, now it’s time to add that framework that you’ve just built into your project. For this part, you can follow GHUnit’s installation instructions as-is. Why rewrite what already works? You don’t have to follow the two optional steps at the bottom of their instructions.
3. Being Awesome With Unit Tests
GHUnit follows standard unit testing framework guidelines: there are methods that are called once at the beginning and end of each test class, and then methods that are called once before and after each test method.
Let’s look at the template test class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
#import <GHUnitIOS/GHUnit.h> @interface YourTestCase : GHTestCase @end @implementation YourTestCase // By default NO, but if you have a UI test or test dependent on running on the main thread return YES - ( BOOL )shouldRunOnMainThread { return NO ; } // Run at start of all tests in the class - ( void )setUpClass { } // Run at end of all tests in the class - ( void )tearDownClass { } // Run before each test method - ( void )setUp { } // Run after each test method - ( void )tearDown { } #pragma mark - #pragma mark Unit Tests - ( void )testMyFunctionality { // Your unit test code GHAssertTrue( false , @"Haha, I set you up to fail." ); } @end |
Note the 2 separate setup methods, -setUpClass
and -setUp
. Either of these can be used for setup/initialization of objects that you’ll use in your tests, but they are different.
Be careful not to use mutable objects (or objects with mutable properties) in thesetUpClass
method. These should go in the setUp
method, which will keep each of your unit tests atomic and independent.
Also, if I am testing a class, I usually add a @property
to the test class in the interface (and synthesize it in the implementation):
1
|
@property (retain) SomeObject *testObject; |
Then, my setup and teardown methods look like this:
1
2
3
4
5
6
7
8
9
10
11
|
// Run before each test method - ( void )setUp { self .testObject = [[[SomeObject alloc] init] autorelease]; } // Run after each test method - ( void )tearDown { self .testObject = nil ; // prevents memory leak } |
That way, when testing, I can just freely reference self.testObject
in each test and know that I have a new, fresh object in each test. Not having to bother with initialization and releasing allows your tests to focus on what they’re supposed to do: test your code.
The -setUpClass
method is useful for more expensive operations like loading test assets. In our case for a recent app, we had property list files that needed to be read into a dictionary. This is immutable data, so we put this in -setUpClass
, defined an instance variable for it, and used it in each test method.
4. Running Your Tests
As part of the GHUnit installation process, you should have created a separate target in your XCode project. Switch to that target, build, and debug your app. That’s it; the GHUnit interface should appear in your simulator/device instead of your app.
A few final “gotchas” and tips:
- Don’t forget to add any libraries/frameworks your app uses to the GHUnit target as well.
- Make sure not to include your
YourTestCase.m
test classes into your app’s regular target. - When using mock data, it’s helpful to put it in a separate directory from your app’s real data. Use the “blue folders” in XCode for this; use NSBundle’s
pathForResource
method to get it out.
There are plenty of resources on the web about how to write effective unit tests – so I won’t delve into that in this post, but I hope that this has provided you with the base to get started writing your own unit tests for Objective-C and iOS.