Test Driven Development in XCode

Test Driven Development, or TDD for short, is a simple software development practice where unit tests, small focused test cases, drive the development forward. This is most easily explained by the Three Rules of TDD that dictate the following:

  1. You are not allowed to write any production code unless it is to make a failing unit test pass.
  2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
  3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

That means that test cases are written before any production code. Not all tests are written up front, it’s rather that a small test is written, then a small piece of production code is written that only allows that test to pass. This is repeated in many small iterations: test, fail, code, pass, test, fail, code, pass…
Many people consider TDD to encourage clean code, simple architectures and a stable system that’s actually testable. Plus, it’s also fun! We’ve previously written about various aspects of TDD, but in this tutorial we’ll focus on how it works for XCode projects, where you write apps for Mac and iPhone. We will create a simple XCode project, do some special configuration steps and then demonstrate how TDD can be used to write your app. We’re going to use OCUnit and its framework SenTestingKit, which nowadays is included with Apple’s XCode tools.

Creating the Calculator project

Let’s start by creating a new project in XCode. You can pick any template, since we aren’t going to touch the generated code. I picked a Window-based iPhone project. Name the project Calculator. Select New Target from the Project menu. Under Cocoa select Unit test Bundle. Call it UnitTests (I’ve had problems with space in the target name so avoid that) and add it to the project. We now need to change some small settings for this target. Select the UnitTests target and hit Command-I for Info. Select the Build tab and locate Other Linker Flags. Replace Cocoa with Foundation. Also remove the entry for GCC_PREFIX_HEADER.

Now it’s time to create your test suite. Create a new group in your project and call it “Test Classes”. Add a new File to this group and make sure it’s a Cocoa->Objective-C test case class. Name it CalculatorTest.m. Uncheck “Also create CalculatorTest.h” since we don’t need a header file. Don’t add it to the Calculator target, but check it to be included in UnitTests. Open CalculatorTest.m file and above the @implementation declaration add:

This is just because we don’t want a rather empty header file. All your test classes will look like this.

Following TDD, we should build right away. First make sure that the active target is “UnitTets” and then press the “Build” button. This will not only do a normal build, but also perform some shell script magic and actually execute the unit tests. Now the build fails of course, since Calculator.h can’t be found.

So let’s add a file which represents production code. Create a new empty Cocoa Touch Objective-C class called Calculator.m (also create the .h file), this is the class that we want to test. Don’t forget to check that they should be included in both your targets. Notice that we mix Cocoa and Cocoa Touch, that’s is fine – all code is executed on your Mac. Build again, and this time you’ll see in the build log that everything built and the test suite was executed, but contained 0 test cases. Let’s remedy that.

Writing test cases

Next we want to write our first test case. Open up CalculatorTest.m and add the following method:

All methods that start with “test” will be recognized as being a test case, and automatically run when building the target. Try and build and you’ll unsurprisingly get the erro “unrecognized selector sent to instance…”. That’s all good. Let’s add the add method to our Calculator class. In Calculator.h add:

Then in Calculator.m add:

That should be enough to let it compile and run, right? Try it out! You’ll see that it indeed build and executes the tests.

Now we change the test so that it actually checks that we get the expected value back. We want to “assert” that the returned value is what we expected. Change the test case to look like this:

Build and you see that the test runs and fails since our calculator always returns 0 regardless of the input. Change the production code so that it returns a+b and rerun the test. Nice, we have a successful build and our test works! Notice the fast cycle of test, code, fix, rerun. The point of TDD is to write tests and production code in small iterations so that you always have something stable (= the tests pass). If you follow the rules of TDD you’ll have a growing amount of test cases and you’ll right away know when something breaks.

Let’s write one more test. Now we want to add the functionality to allow one number to be divided by another. Start up like before with a new test case like this:

You see, this time we expect to get back a float. Add the corresponding divide method to Calculator.h and Calculator.m. If you need help see the full code listing at the end of this article. You might notice that just returning a/b gives you 2.0. Type cast the return value to (float)a/b and you’ll be fine. Go ahead when you got it working.

What happens if you try to divide something with zero? Well, why not add a test case for this scenario? If you’re into maths you know that the result of this operation is undefined. How do you expect something to be undefined? This is tricky. :-) Start off by just expecting any number and see what you get back. As you see, the float value “inf” is returned. It seems like Objective-C treats the zero as “a value approaching zero” and that will indeed result in infinity in a division. But, hey, that might not be what we want our calculator to do. Let’s change the test case to expect an exception to be raised instead:

Build and notice the error message when no exception was raised. Now change the production code to something like this:

Rerun and smile as the test passes! There are many different variants of asserts you can do with SenTestingKit. Compare objects with STAssertEqualObjects. STAssertTrue to check that a returned boolean is true. Open up SentTestCase_Macros.h if you want to see what you can play around with. By the way. You might have tried using NSLog in your test cases (just to experiment). This is nothing you would do in real life, as you want all necessary information in the fail message and output nothing if the test passes. Anyway, since the tests are actually run using a separate shell script for the UnitTest target you won’t see the log in your console as usual. Instead check the build log and click the “text” icon to the right for the “Run test suite” step.

Wrapping up

Finally, if you look at your test class you might notice that we’re allocating a new Calculator for every test case, and we never release them. That’s no good. Luckily there are setUp and tearDown methods that will be launched automatically before and after each test case. Change your implementation to look like this (this is the final listing):

For completeness, here’s the listing for Calculator.h:

and for Calculator.m:

Next blog post we’ll see how to automate the running of test cases on a build server called Hudson!

This Post Has 16 Comments

  1. Thanks nice post. I’ll try it tonight. When will you incorporate this using Hudson?

  2. Thanks. I hope to have the Hudson blog post out next week.

  3. I am rather new to objective-c (been a java guy for a decade) and was curious to know why you are using includes (not imports) and if are there syntax errors in your example files? Should the header/implementation file contain this declaration instead?

    #import
    #import “Calculator.h”
    @interface CalculatorTest : SenTestCase
    {
    Calculator *calc;
    }
    @end
    @implementation CalculatorTest
    // .. snipped test cases for space
    @end

    I noticed that you used “#include ” but the actual path/name is “”. This might be working on your machine because your file system isn’t configured to be case sensitive. I did get mine working with the updated info though.

    Thanks for your post!

  4. Looks like my post got scrubbed of the < & > symbols. Trying again with escaped HTML;

    #import <SenTestingKit/SenTestingKit.h>
    #import "Calculator.h"
    @interface CalculatorTest : SenTestCase
    {
    Calculator *calc;
    }
    @end
    @implementation CalculatorTest
    // .. snipped test cases for space
    @end

  5. Thanks, Briggs. It’s supposed to be ‘#import’ and I’ve updated the article. I wonder if one of the templates in XCode inserted ‘#include’ instead.

  6. Thank you so much for the tutorial. TDD is my goal and you’ve shown me how to get there.

  7. Hi,
    I am trying to create a Document-based application in Lion OS. In the .nib file i am using NSBrowser, i set the maximum column as 3 and Autosizing in all the direction. When i run the application i am getting only 2 column but after selecting or resizing the window i am able to see all the 3 columns.

    Please let me know that it is a Lion OS issue or Xcode issue.

    Thanks in Advance,
    Arun

  8. Hi,
    Nice tutorial. It helps me to start TDD in iOS. Good work. keep going.

  9. Hi Christian,
    Good Post, but I have a question. Sometimes I don’t understand why I need build test to my projects. Why is the best advantage you do it ? For Instance, you need to create some short App. is it necessary you do it ? or just for big or Games apps ?

    1. Hi Emerson,

      It’s not “necessary” to write tests for your code, but a lot of people think it’s a very good practice. I’d say it’s a mindset. By using Test-Driven Development you write tests first to drive your development and architecture. Again, it’s not necessary. Sometimes writing tests, even for very small programs, can help you get an algorithm right. It’s often worth the investment of writing tests and setting up a testing environment. The more time you spend with your program, the more you’ll gain.

Leave a Reply

Close Menu