If you’re trying to do quality iPhone development, TDD is not optional. Fortunately, it is also not impossible.
Test Driven Development (TDD) has an uncertain relationship with iPhone development. OCUnit and Unit Test targets were not added to the XCode development toolkit until late in the game and much of the Cocoa and UIKit frameworks used by iPhone developers every day were not developed with unit testing in mind. A look around the web will find you developers who have become so frustrated with the process they simply gave up.
The situation is unfortunate and familiar. In the years before I came to my senses I wrote C and C++ code on Windows, and it was hardly easy to write tests then. When developers wrote their first Win32 program did they say, “Unit Testing isn’t integrated with the environment. I give up!” Well yes, many of them did, but not the good ones, and now the developer working in those languages and on that beloved platform no longer has to worry about too few options, but too many.
The same process is repeating itself in iPhone development. OCUnit now runs on iPhone, and is built into XCode. Even before that the Google Toolbox for Mac (GTM) had been released, and for a unique implementation you can try out GHUnit, which displays the results on the phone itself. OCMock worked on the iPhone almost immediately, and Cocoa itself actually isn’t as resistant to unit testing as originally thought, if you’re willing to be patient.
To demonstrate, let’s work through a fun sample application on the iPhone.
To get started with your first Test Driven iPhone application you’ll need a Mac with a few things installed:
XCode—preferably the most recent version.
The iPhone SDK, again hopefully the most recent version.
This is not an article on Objective-C or iPhone development, so I’ll assume at least a cursory familiarity with the toolset. Hopefully even iPhone novices will be able to follow along, and this article is meant to be done with a computer in front of you. At times I’ll be skipping simple steps, so just copying the code snippets isn’t going to produce a working program and besides copying and pasting the code is just cheating. Do cheaters ever prosper? Don’t answer that.
Setting Up Unit Testing
There are several options for unit testing iPhone applications. My personal preference is the Google Toolbox because it gets features faster than OCUnit, but that isn’t to denigrate either of those other excellent projects, or any other Unit Testing framework I may have missed. It’s simply the one I’m most comfortable with. In addition I’ve made a couple of Unit Test templates that will make getting set up with GTM a snap. To install the templates from my git repository, run the following at the command prompt:
The sudo is necessary because the templates are installed in a system directory. Now we can easily create a new project with Unit Testing built in. Start up XCode and create a New Project. That screen will give you your choice of projects, and you should see a new category under iPhone OS called Google Toolbox. Choosing it will show you two templates for Unit Testing (OpenGL ES and View Based Application). Why only those two, and not the plethora of other templates offered by Apple with Unit Tests attached? Because I’m selfish—these are the templates I use. If you someday make your own, please make a pull request on the github repo.
Some Ping Pong
Create a View Based template by selecting it and clicking choose, then name your project PingPongScorer, since we’ll be writing a simple program to score a ping pong game. It will keep track of score, serve, and display a winner. Go ahead and build the project; you should see no errors and no warnings.
Let’s make the front end of this application look the way we want it to. Open the file MyCurrentViewLocationViewController.xib and make your view look something like this:
I can think of a few stories to implement:
When you touch either score, increment the point total by 1.
When any two points occur, the serve should switch sides.
When either player reaches 11 points, there should be an alert announcing the winner.
New Game should set both players points to 0 and the server to player 1.
None of this should be particularly difficult to do, especially for smart guys like us, so let’s get started by creating a test case. Choose File->New and select the GoogleToolbox entry under iPhoneOS, then select the Google Toolbox test case. We’ll start by writing tests for the controller since it responds to the touch events and we’re working outside-in. Name the file PingPongScorerViewControllerTest and make sure the file is only in the Unit Test target. Now build the project again and you should see the build fail. If you open the error you’ll see that the code compiles but the template we’re using starts with a failing test. Note that in GTM all test cases inherit from GTMTestCase and all tests are methods that begin with the word test. So when you built your program it ran all the unit tests as a post-build step and failed.
We’ll replace that test and start by writing the test that seems simplest to me—incrementing the score for player one. Let’s call scorePlayerOnePoint once, and verify that the new score is 1.
You’ll get an error because the property playerOneScore doesn’t actually exist yet, and another because scorePlayerOnePoint doesn’t exist. Normally I would have stopped at the method call, then implemented just enough to compile, then come back and finished the test, but for the sake of brevity I’ve done this all in one step. Let’s look at the assertion STAssertEqualStrings, one of the many built into GTM. It takes the actual value for its first parameter, and the expected value for the second. In this case we are checking against the UIButton playerOneScore’s currentTitle field. That’s a read-only property on UIButtons, and I’ll show you how to change it in a second. I also pass in nil for the third argument, which is an optional string format for the error message. If you pass in nil you’ll get the default, which is usually good enough. I’ve also gone ahead and added this controller to the autorelease pool for now, since this the only place I’m using it. Looking at the scorePlayerOnePoint message you’ll see I pass in nil; that’s because all Cocoa actions take the sender as a parameter, but I don’t actually care what the sender is for my implementation. Let’s make this pass in the simplest way possible.
That’s a mouthful for simple isn’t it? To set the title for a UIButton you actually set it for a state—but in this case we want to set it for all the states (normal, highlighted and selected) so we bitwise and the flags together. The simple part is the hard-coded “1”. That was pedantic of me, wasn’t it, but this is an excellent example of why I do that. I didn’t have the API for setTitle memorized and had to try it several times before I got it right. By hard-coding in a one I was able to narrow down the reasons why the test wasn’t passing to just the API call. This is also why we don’t mock out UIKit objects: we really do need to know it’s calling the real object and working correctly. Now I’m going to write another test to triangulate—what happens when the player scores twice?
Aha, a failing test, now I can write the real implementation.
We now have a simple way to make player one score, so I’ll go ahead and make player two score now so I can wire this up in Interface Builder (IB) and see some action. Here are my tests and code:
Ohhh the duplication, it burns—but before we fix that, let’s look at a few things about this. Take note of the setUp and tearDown methods. Like many other XUnit based frameworks, GTM will allow you to create a setUp method that will be run before every test, and a tearDown that is executed after. If you name the methods that they are called as you’d expect. The controller itself is now defined as an iVar in the class definition so that we stop defining it in every test. This means we need to do a proper alloc and release rather than using autorelease. It also turns out my hunch was right—what I really want is a controller that contains two player classes and that shuffles the data between the players and the UIButtons. At this point we have several options. We can do the extract class refactoring, and keep the tests here. We can test-drive new objects, then insert them, then switch these to mocks. We can throw this out and start over even, I suppose. This isn’t a straight extract class refactoring however, so what we’ll do is start writing the Player interface through a mock object, then change the previous tests to use the stubs instead of doing math and converting back and forth. This has the advantage of demonstrating one way to use mocks when doing TDD on the iPhone. Here’s the next test.
This time we really will stop when the test doesn’t compile. The reason is that this test fails is that I don’t have a playerOne property or a MockPlayer class. Let’s make the first half of that pass. Here’s how my playerOne object is defined in the controller class.
See how the player is defined—NSObject<Player>. That means this is an object of type NSObject conforming to the protocol Player. A protocol is just Objective-C for interface and is made up of a list of methods that this object will respond to. I could actually define this object as a type of id, which means it could be anything as long as it conforms to the protocol, but I use NSObject instead. The reason is that I know I’ll be using at least one NSObject method on this class, release, and all objects in Cocoa inherit from NSObject anyway.
Let’s go ahead and create the Protocol using File->New->Cocoa Touch Class->Objective-C protocol. XCode only recently added the protocol choice to that menu, so if you don’t have it just choose a C header file. Name it player, and if you chose the Objective-C Protocol the file should contain the code below. If it doesn’t already, add it.
You’ll also need to import the player in the header file for PingPongViewController. This should leave you with one error after building—MockPlayer undeclared. Go ahead and create the class, making sure it inherits from NSObject and implements the Player protocol. It also should only belong to the Unit Tests project. The new class doesn’t have any methods because we haven’t had a reason to create them. I’ll point out now that the reason we’re hand-rolling this mock object instead of using OCMock is entirely personal preference. I have used OCMock before, and it’s an excellent framework, but its use is outside the scope of this article.
Let’s test-drive this object’s interface into existence. When the action scorePlayerOne is called, I want player one to...score. That’s simple. I’ve made the earlier incomplete test into this.
One of the advantages of rolling your own mocks is that you can still have the setup, execute, check order in your tests. We’ve got a new assertion too, STAssertTrue, which is the root assertion from which all others spring forth. You’ll get a compiler error because scored isn’t set on the mock object, so let’s set that up with a simple property. Once you create the property you should see a failure again. You’ll see on STAssertTrue that the default error message really isn’t good enough—it says something like
That's pretty generic, and for true false assertions I like to put i some more readable text. Change the assertion to read as follows:
You’ll still get that ugly error message from before, but the message you wrote in will be appended to the end of it. To make this test pass, let’s go ahead and write a little method called score that just sets the Mock object’s scored property. This method is in the MockPlayer.
Do you see what we did there? We just defined a method we’ll need in our interface, in the terms of the code using it, without actually implementing the real object. Very cool. Let’s get the method into the protocol:
That was tough. Our test is still failing, so call score in the scorePlayerOnePoint method.
Next we need to make sure that we call score and then set the button’s text to the not-yet-existent currentScore property on the PlayerOne object. Doing that will mean breaking the previous tests, and I don’t want to do that, so now I’m going to test-drive a CurrentPlayer class, which will also use the Player protocol. That’s a very straightforward class so I’m not going to go step by step through it right now. I trust you can do it; just remember to keep CurrentPlayerTest in only your UnitTests target, but put CurrentPlayer in both targets.
Are you done? Let’s start integrating our new player object with the code. Change the testScorePlayerOnePointUpdatesPlayerOneText in the view controller to read as follows.
Now in this test we set the current score on the mock object, and we verify that’s what the button’s title is changed to after the call. We’re using an explicit setter for the currentScore because we’re going to define currentScore as readonly in the protocol. That’s not strictly necessary but there’s no reason to add a setter on the real object either since only the tests want to explicitly set the score. We immediately get a warning that player may not respond to this message, so let’s go implement it.
Now indeed upon reflection these mocks are a little close to the implementation of the real objects, and I could have used real objects for testing, but I’m going to stick with the mocks because a) it’s an exercise and b) it’s decoupled the implementation of scoring from the controller. When we make this test pass, the controller will no longer know that the score is worth a point, and that decoupling is worth a small amount of indirection. Speaking of which, let’s make it pass.
When we make this test pass, the testScoreTwiceUpdatesPlayerOneTextToTwo fails because it doesn’t make sense anymore. The View Controller doesn’t do math, so you can delete that test. I assume you have a similar test in your Current Player class. You do, right? Unfortunately this passing code is still wrong because score needs to be called before the text is set. We can encode that into the mock. The currentScore getter (Objective-C novices—properties are just getters and setters generated for you by the compiler) will always return 0 unless score is called first.
When we do that, testScorePlayerOnePointUpdatesPlayerOneText fails as it should. Let’s flip the order of the calls to score and currentScore to see the tests go green. Once again I will leave updating Player2 up to you. You may want to use a method like updateScreen to update both screen items from their player objects.
Now we have a true controller that updates objects and refreshes the screen. We only have one more thing left to deal with before we wire this up. Experienced Objective-C programmers are banging their heads against the table right now muttering, “but... but... the memory!” and yes, it’s true that this program has memory leaks. None of the objects on the controller are released as they should be. Test-driving memory management in Objective-C can be done in two ways. The first is to add memory leak detection to your test suite, which GTM has built in, but the problem is that many of Apple’s own frameworks leak or get reported as leaks, so it can become impossible to discern the real leak you created. Instead we’ll be checking the retain count of each object after we delete the controller. Here’s our next test.
This test is pretty complicated, so let’s take a minute to explain. This test creates a player and sets it on the controller. When we call alloc the retain count of the object is set to 1, and when we set the playerOne property, the retainCount goes to 2 because the property is declared with the retain flag. When we release the controller it should release the playerOne object in its dealloc method because it retained it, but it doesn’t, so our test fails. We’re using STAssertEquals here, which checks an actual value vs. an expected value. Finally we release the test’s copy of the player after the assertion. To make this pass, just release the playerOne object in the controller’s dealloc method.
Once you have that passing, write tests for playerTwo, playerOneScore and playerTwoScore. We should encapsulate that check into the method below.
I’ve cleaned it up a little bit from the original implementation and with a little more work we could probably turn this into a generic exception we use everywhere. Then each of the tests for deletion is just one line of code.
The errors will show up in the editor in the assertOwnedObject method, and not in the test as we’re accustomed to, but if you look at the build output you can see where the error actually occurs.
The controller is releasing all the objects it owns, so we’ll have to create both the buttons and the players in the setUp method, which is appropriate since when we run the application it will do the same thing.
Seeing the app
Let’s wire up the view in IB with the objects we have and see how the program is working so far. In order to do that in IB we’ll need to add some macros to the PingPongScorerViewController.h file so that it can see our outlets and actions. Here’s what PingPongViewController.h looks like after the changes:
When you build and run the tests, you won’t get a failure because IBAction and IBOutlet don’t actually change any behavior, they just make it possible for IB to find them. Now go ahead and open up PingPongScorerViewController.xib. One of my favorite things about it is that you can add any objects to the XIB file, not just UI objects. This means we can inject any dependencies here: our code is none the wiser. Dependency Injection without XML, hooray! In the Library, choose the Object type, and drag it over to the Document window. Double-click that object to open its Inspector, click the Identity tab, and change the class to CurrentPlayer. Do this a second time for player two. You can use the name field to make the two objects easy to tell apart. Your Document window should look something like this:
Now when this view controller is loaded, those two players will be created and we just connect them to the view controller. Hold Ctrl and drag from the File’s Owner object, which is our PingPongScorereViewController, to Player One. You should get a little dialog with outlets and playerOne should be a choice. Do the same for Player Two, and both buttons. Finally we need to create actions for both buttons, so again Ctrl-Drag from each button to the File’s Owner and select scorePlayerOne and scorePlayerTwo as appropriate. Open the File’s Owner inspector and click the outlets icon. If you’re wired up correctly you should see something like this.
Now build and run the app. When you click the buttons in the simulator they should increment. Story number one complete!
Sadly our time here is up, but I’ll be continuing this app on my github account at github.com/paytonrules/PingPongScorer.git. We’ve taken a bit of a circular route to get here, we’ve done some refactorings, we’ll do more. We’ve tested memory leaks, we’ve used Protocols, and we’ve written the beginnings of an application in iPhone. Hopefully it’s clear that if you’re trying to do quality iPhone development that TDD is not optional, nor is it impossible or really much more difficult than normal development.
Exercises for the Reader
Continue implementing the stories above, naturally.
Try doing the implementation by extracting the class instead of test-driving a new object, and see where it leads you.
Upon writing the two player models I realized I still had duplication, just of a different sort, between the objects and the buttons. Rather than manually shuttling the value from the player object to the view, we could solve this by setting up key-value observing. This is a more advanced test, but is definitely doable. The key is to remember you’re testing that when the observed changes then the observer is changed.
Eric has over ten years software development experience, going back to when he started a web design company in college with a $500 scholarship. After graduating he took his first “real job” at the now defunct Mission Studios where he worked on JetFighter IV: Fortress America. After the company dissolved he moved on to Siemens Building Technologies, where he developed Windows applications both large and small on a two million-line legacy code base. He gained professional experience in C, C++, C# and learned Ruby. Since joining 8th Light Eric has worked on projects in Java, C++, and Ruby on Rails projects, and is pursuing his Masters Degree in Software Engineering at DePaul University.