Getting Started Writing iPhone Unit Tests

Alright, well hopefully you have read the first two parts of this series. If not, then go back and read Is iPhone Unit Testing Possible? and How to Create an iPhone Project in Xcode That Can Run Unit Tests.

Make sure that you have an iPhone Project that is already set up for unit testing. You can either follow the steps in those last two posts or you can download a pre-made “example1” iPhone Project that can run unit tests that I uploaded to GitHub.

In this tutorial, I will be showing you, the iPhone Developer, how to get started writing unit tests for your iPhone App. I will be doing this in the TDD style – where TDD == Test-Driven Development. In other words, we will write the tests first. You don’t have to do it this way, but I thought I’d show it to you since it is a nice practice that was ingrained in me from the Ruby on Rails community. This is not to say that it started in Rails – it really started back in the Smalltalk days with Kent Beck and the other original XPers (eXtreme Programming). But being involved in the Smalltalk, Java, Objective-C and Ruby communities in my career since 1995, I haven’t been seen such testing dedication as I have in the Ruby on Rails community. I hope it is something that really spreads in iPhone community as well.

So, how do we get started? Or in TDD-speak, how do we write our first failing test? You write failing tests first as sort of a TODO to yourself as a programmer. When you get it to pass, then you can mark off your TODO, but in a nice programmatic way.

You will need to create a Test Class. Then you’ll add a Test Method. This should reference a non-existent Class, which you’ll then proceed to flesh out. This is the classic Test Driven-Development (TDD) way of testing.

Create a new group with Project > New Group. Name this new group “Tests”.

Create a new Test Class with File > New File… and choose Cocoa Touch Classes / NSObject subclass.

Name your class “FooTest.m”. Make sure that it is only a member of the Tests target.

Run the tests now by pressing the Build button. Make sure that the “Tests” target is selected in the Active Target dropdown and “Simulator” is chosen a the Active SDK. Notice that the Test Class isn’t being included yet:

Test Suite '/Users/louie/builds/Debug-iphonesimulator/Tests.app' started at 2009-02-19 14:43:32 -0500
Test Suite 'SenTestCase' started at 2009-02-19 14:43:32 -0500
Test Suite 'SenTestCase' finished at 2009-02-19 14:43:32 -0500.
Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.000) seconds
Test Suite 'GTMTestCase' started at 2009-02-19 14:43:32 -0500
Test Suite 'GTMTestCase' finished at 2009-02-19 14:43:32 -0500.
Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.000) seconds
Test Suite '/Users/louie/builds/Debug-iphonesimulator/Tests.app' finished at 2009-02-19 14:43:32 -0500.
Executed 0 tests, with 0 failures (0 unexpected) in 0.001 (0.001) seconds

This Test Class doesn’t know it is supposed to contain tests yet. Let us change that.

Change the import statements to include “GTMSenTestCase.h” This is the Google Toolbox for Mac version of SenTestCase. This enables your test cases to be found by the GTM RunIPhoneUnitTest script. It also adds some additional capabilities we will cover later.

Change the superclass to be “SenTestCase.”

Your class header FooTest.h should now look like:

#import "GTMSenTestCase.h"


@interface FooTest : SenTestCase {

}

@end

If you run the tests now, you’ll see that FooTest is now being included:

Test Suite '/Users/louie/builds/Debug-iphonesimulator/Tests.app' started at 2009-02-19 14:44:30 -0500
Test Suite 'SenTestCase' started at 2009-02-19 14:44:30 -0500
Test Suite 'SenTestCase' finished at 2009-02-19 14:44:30 -0500.
Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.000) seconds
Test Suite 'FooTest' started at 2009-02-19 14:44:30 -0500
Test Suite 'FooTest' finished at 2009-02-19 14:44:30 -0500.
Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.000) seconds
Test Suite 'GTMTestCase' started at 2009-02-19 14:44:30 -0500
Test Suite 'GTMTestCase' finished at 2009-02-19 14:44:30 -0500.
Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.000) seconds
Test Suite '/Users/louie/builds/Debug-iphonesimulator/Tests.app' finished at 2009-02-19 14:44:30 -0500.
Executed 0 tests, with 0 failures (0 unexpected) in 0.002 (0.002) seconds

However, with 0 tests, FooTest is pretty boring. Let’s spice up FooTest. Switch over to FooTest.m. You can quickly do this with the keyboard shortcut Command + Option + up arrow.

Now add a Test Method. Test Methods are where the work happens with testing. They must start with “test” and the first character of “test” must be a lowercase “t” in order to be found by the test runner script.

Add this piece of code:

- (void)testBar {
	Foo *instance = [[[Foo alloc] init] autorelease];
	STAssertEquals(@"bar", [instance bar], nil);
}

Try to Build. You’ll get 3 errors. They all relate to Foo not being declared in FooTest. This actually is good! Well, in the classic TDD sense. What we have done is written what we want to happen when a client calls Foo and what the client expects back when it calls the bar method of Foo. Specifically, we want it to return the NSString @”bar”. This can also serve as good documentation for future programmers who are inspecting this code to see an example of how to use Foo.

Now let us fix these errors:

Select the Classes group.
Create a new Class with File > New File… and choose Cocoa Touch Classes / NSObject subclass. Yes, this is just like how we created a new Test Class. They are the same because we haven’t created a new template for a Test Class.

Name your new class “Foo”.

Make sure that it is a member of both of the targets “example1” and “Tests”. The reason we want this is because Foo should exist in the main application but also should be available for testing. Going back, we can see that FooTest was only in the Tests target because we don’t want to ship our tests with the application. This will make it lighter and thus quicker to download when a user decides to purchase it from the App Store. Quicker downloads should mean a quicker path to riches! :)

Now try building again. Still 3 errors? Yes, because we need to make Foo known to FooTest.

Go to FooTest.h and add the line:

#import "Foo.h"

Now build again. You should have two errors now and four warnings.

Now Xcode tells you that “warning: ‘Foo’ may not respond to ‘-bar’.” Again this seems wrong, but this is actually a good thing with classic TDD. This just means that we need to declare the method bar. So let us do just that.

Go to Foo.h.

Add the method declaration:

- (NSString *)bar;

Go to Foo.m.

Add the method implementation:

- (NSString *)bar {
	return @"bar";
} 

Now build and you have success! This is known in TDD-speak as “green bar.” When you had failures due to errors/warnings, that was “red bar.” This had to do with the graphical indicators of the tools at the dawn of TDD. We can just call them “success” and “having test failures.”

This method is what I’ve used over the past year to help ensure good code quality in that iPhone apps I have built for my consulting clients. At this point, you know enough to be able to write unit tests, the iPhone way with the help of Google Toolbox for Mac. You can start to use more generic unit testing techniques that you may have used in other languages. There are also some iPhone-specific testing techniques that I will cover in the future if people are interested.

One Reply to “Getting Started Writing iPhone Unit Tests”

  1. This is great material, I really appreciate you sharing this! It has helped me tremendously. I’m new to Objective-C (8 weeks sofar) and have 10 years commercial experience predominantly in java so I’m very well versed with TDD. Great to have it available again :)

    I literally wasted over a day trying to get the “built in” testing working with XCode/iPhone and there were just endless minor problems which none of the other write-ups could help me with. I’m glad you brought your experience to the table and especially your effectiveness of communication.

Comments are closed.