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!

12 Responses to “Test Driven Development in XCode”

  1. [...] Test Driven Development in XCode | Jayway Team Blog – Sharing …Jan 15, 2010 … Test Driven Development, or TDD for short, is a simple software … how it works for XCode projects, where you write apps for Mac and iPhone. [...]

  2. Ayon says:

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

  3. Arun says:

    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

  4. Bob Sargent says:

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

  5. [...] to help me with configuring my first unit test… so far so good. I also found one article on Test Driven Development, but I haven’t gotten to try that one [...]

  6. Christian Hedin says:

    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.

  7. Briggs says:

    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

  8. Briggs says:

    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!

  9. [...] Test Driven Development in XCode XCode を使ったTDDについて解説しています。TDDの参考になりますよ。 [...]

  10. [...] Test Driven Development in XCode – TDD for XCode [...]

  11. Christian Hedin says:

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

  12. Marcus says:

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

Leave a Reply