First Experience with Unit Testing for the iPhone

After attending three of the Tuesday evening iPhone study group sessions I was starting to feel a little guilty that the only iPhone development I had accomplished so far was to downloading the developer kit a few times from Apple.

Trying to make amends I finally tried my hand at some coding on the holiday Monday. Having never coded before in Objective-C, nor used the Xcode development environment before, and barely using my iPod touch in the last year, there were a few obstacles on the way.

Using the built in templates and some copy-and-pasting from the Pragmatic Programmers iPhone SDK Development book I found it didn’t take long to get a simple app with a list/table up on the screen. At which stage I needed a model of data items to back the list; my first Objective C coding from scratch felt like the time to be in the position to start unit testing.

Rom had given a very useful introduction into automated testing for the study group a week or so before. Regrettably my memory of the details wasn’t good enough to piece together the actions of setting up the environment so it turned out to be quite a trying process for me.

There seem to be two variants of automated testing supported by Xcode out of the box. The first is unit testing, or logic tests, that are triggered as part of the compile/link phase and can make use of the basic parts of the library but you can not make any use of the UI or device features.

The second are something closer to functional tests, or application tests, that are hosted and run on the physical device itself that can make use of the UI and device features. They might not even run on the development tools simulator.

Somehow I wasted a couple of hours on this before I got my first couple of tests to run “green”. Automated testing isn’t baked in as cleanly as jUnit testing on Eclipse. It does come with a basic unit testing library built-in, modeled after a very old version of xUnit, and also some wizard templates to help with the creation of sample test classes and targets.

However, additional steps are required for setting up your projects to run the two different kinds of tests. I suffered from too many recipes to choose from: a few from Apple, lots more on the web, all different from each other. Along the way I was hit by Bus Errors, linking problems, unintelligible failures, and so on. After starting from scratch a few times I was able to get the unit tests working but have given up on the functional tests for a while. What is probably a 30 second task to someone who knows their way around this world just exploded on me.

At this point I can write and run unit tests as part of the Xcode build. There seems to be no visual feedback while running the tests, so no red or green bar to hang your hopes on. Rather a failure will cause the compile/link to fail and the build results provides access to what went wrong. The initial form of this is onerous to use as you are effectively going through the log of running each test.

To speed up the testing cycle I found the following helpful:

  • Set auto-save before a build: Xcode -> Preferences -> Building -> For Unsaved Files: Always Save
  • Trigger running the tests with a build with Option-B
  • To see the test results: Click Failed in the footer to show the Build Results then select By Issue to reduce it down to just the list of test failures.

The built in testing framework is not comparable to how we would write Java unit tests today. So all you get is a few STAssertX methods, which for some reason have a mandatory description argument.

- (void) testShouldBe50States {
    DataSet* dataSet =[[DataSetExamples alloc] usStatesElevation];

    STAssertTrue(50 == [dataSet rowCount], nil);
}

To get assertThat(x,y), from Hamcrest, and also support for mock objects, from OCMock, follow the setup instructions from the following page:

http://cheezyrants.blogspot.com/2010/01/bdd-for-iphone.html

With this in place the unit tests themselves are starting to look promising.

- (void) testDataForWyoming {
    DataSet* dataSet =[[DataSetExamples alloc] usStatesElevation];

	assertThat([dataSet row: 49 cell: 0], equalTo(@"Wyoming"));
	assertThat([dataSet row: 49 cell: 1], equalTo([NSNumber numberWithDouble:13809]));
}

When it comes to running the tests, the build process is so far reasonably prompt, still I miss the jUnit green bar or the DevCreek ticker.

I haven’t got so far to set up an continuous integration machine that runs the tests too, though there seems to be some support for that. See the tail of:
http://blog.carbonfive.com/2009/02/testing/iphone-unit-testing-toolkit

Some open questions that perhaps others could help me with:

  • How come test failures aren’t marked as errors in the source code? I have to use the build results to see them. Is there a preference that I need to switch on?
  • The assertThat(x, y) only seems to work with objects, not “primitives” such as int/NSUInteger and so on; it complains about casting an integer to a pointer. For now I just use the old STAssertX methods but I would obviously prefer to use the variant of assertThat. Am I missing something?
  • It doesn’t seem that the Xcode development environment can be extended to the same degree as Eclipse. Is that so? For example could a JUnit style test runner/dialogue be integrated.
  • Is it really the case that I have pay Apple $100 so that I can deploy an application that I wrote onto an iPod touch that I own?
It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

Leave a Reply