AddThis Social Bookmark Button

Print

Unit Testing with OCUnit

by Jim Menard
04/23/2004

Are you sure your code works -- all of it? If you make a change in one place, are you sure you haven't broken something else? When you fix a bug, how do you know that it stays fixed?

Testing frameworks helps you make sure. They provide a way to write and run unit tests consisting of test cases: groups of small tests that exercise a particular class or feature. For example, one test case may exercise a Checkbook class, making sure that it adds and deletes entries properly and returns the correct account balance value. Another test case may exercise a CheckbookEntry class, making sure that it accepts and returns the monetary amount you give it.

Much of the XUnit literature describes the ideal development process as follows.

  1. Write a test case for a single feature.
  2. Run the test and watch it fail.
  3. Write the code that implements the feature.
  4. Make it work.
  5. GOTO 1.

This is called Test-Driven Development or Test First Development. Practically speaking, I find myself writing the code first and then writing a test case for it soon after. My development process ends up being:

Related Reading

Learning Cocoa with Objective-C
By James Duncan Davidson, Apple Computer, Inc.

  1. Write a large chunk of the application.
  2. Write test suites that exercise everything written so far.
  3. Fix the bugs found by the tests.
  4. Write a few more tests; fix some more bugs.
  5. Add new features.
  6. Write tests for the new features.
  7. GOTO 5.

This process lets me experiment and make a few design mistakes during step 1, perhaps starting from scratch a few times. Since I wait before writing the initial set of test cases, I don't have to write and rewrite tests that are exercising code that I'm probably going to throw away.

When I developed the simple OS X checkbook register program CheckbooX, I didn't write any unit tests. I used the program myself, but had no plans to release it. To further excuse this unconscionable behavior, I was not aware of any XUnit testing frameworks for Objective-C at the time. In this article, I am going to describe testing frameworks and walk through the process of adding unit tests to CheckbooX. The complete unit tests for CheckbooX are included in the source.

Adding unit tests to an existing application is harder than starting out with unit tests, for two reasons. First, your classes are not designed with testing in mind. For example, they may be tightly coupled, making it difficult to write tests that exercise single classes or application features. Second, motivating yourself to write tests for code you already know works can be difficult.

Testing Frameworks

Writing an individual test case isn't hard. Running all of your test cases and interpreting the results can be, unless you use a testing framework that takes care of all of those details for you.

There is a family of related testing frameworks, collectively called XUnit, that includes JUnit for Java; SUnit for Smalltalk; RUnit, which now ships with Ruby; and more. Until recently, I wasn't aware of any XUnit testing frameworks for Objective-C. I didn't look hard enough. There are at least two: OCUnit by Sen:te and TestKit by Both are open source projects and are freely available. TestKit works with ProjectBuilder, and OCUnit works with Xcode or ProjectBuilder. They both include programs that let you run your tests from the command line. OCUnit also lets you run your tests from within Xcode or ProjectBuilder. TestKit comes with a separate GUI test runner application.

Most test frameworks let you run one or more suites of tests. Each suite is most often made up of all of the methods in a single class that have names that begin with "test." Languages such as Java, Ruby, and Objective-C allow the framework to discover such methods at runtime.

The output of a test run is usually a summary of the test results: X tests passed, Y tests failed. If there are any failures, the frameworks usually display the error messages you supplied or those supplied by your runtime environment. Note that "failure" is relative: if you want your code to fail, then you can write a test that makes sure that it fails properly. When your code fails, the test succeeds. Some testing frameworks include a GUI application that show you a "green light" if all tests pass or a "red light" if something went wrong.

A single test contains one or more assertions: statements that, when false, signal an error. OCUnit defines a series of macros such as STAssertEquals, STAssertEqualObjects, STAssertTrue, and STFail. Here is an example test:


- (void) testEntryAmount {
    // entry is created in setUp; see below
    [entry setAmountInPennies:123];
    STAssertEquals(123L, [entry amountInPennies],
                   @"bad amount; 123 != %ld",
                   [entry amountInPennies]);
    STAssertEquals((float)1.23, [entry amount],
                   @"bad amount; 1.23 != %f",
                   [entry amount]);
}

The custom bad amount error messages aren't really all that useful; OCUnit shows you the two values if they don't match.

The macro STAssertEquals takes at least three arguments. The first two are the values to be compared. The third is the message to print if the test fails. It is not optional, but it may be nil. This argument is just like an NSLog string; it is a format string and any number of additional arguments may be used.

Since a test case often needs an object to test, testing frameworks provide a way to set up common conditions before each test and to tear down after each test is run. These methods, cleverly called setUp and tearDown in OCUnit, are run before and after each test method is run. For example, the test class that contains the testEntryAmount method also contains the code:


- (void) setUp {
    // entry is declared in the @interface;
    // that is not shown here.
    entry = [[CheckbookEntry alloc] init];
}

- (void) tearDown {
    [entry release];
}

Writing test cases for non-GUI code is easy. Testing your GUI is harder. There are testing frameworks out there for web-based GUIs, Java GUIs, and more. This article completely glosses over this important aspect of application testing. Performing a web search for a phrase like "gui testing framework" will help you find plenty of relevant information.

Installing OCUnit

When you download OCUnit, you must choose which version you want: Xcode or ProjectBuilder. Additionally, you need to choose either a version with an installer that puts OCUnit in the root level of your hard drive, or a version that comes with a script that installs OCUnit in your home directory. Some of the versions are disk images, some are .zip files, and some are tarballs. The disk images are for the Mac-OS-X-only versions of OCUnit. The versions of OCUnit that work equally well with GNUSTEP, Rhapsody, and friends are packaged as tarballs.

I downloaded OCUnitRoot-v37.dmg, which is the version for Xcode that installs OCUnit at the root level. The installer launched automatically and showed me exactly what it would be installing and where it goes. The documentation gets installed at /Developer/Source/OCUnit/Documentation/index.html.

OCUnit adds a number of templates to those available when you create a new project. For example, the "Cocoa Application + Test" template creates a project that links against Cocoa and the SetTestingKit frameworks. You need to decide where you want the test cases to go: in a separate project, in the same project but a different target, or in the same project and target.

To add OCUnit tests to CheckbooX, I decided to create a new target in the same project and to configure the project so that the tests were run each time the test target is built. Following the instructions in the OCUnit documentation, here is what I did:

  1. Opened the CheckbooX project.
  2. Right-clicked on "Targets" and selected "Add/New Target."
  3. In the dialog that appeared, selected "Test Framework" from under "Cocoa."
  4. Named it "Testing" and clicked "Finish."
  5. Noticed that the new target already has a shell script build phase and the build setting TEST_AFTER_BUILD as described in the OCUnit documentation.
  6. Found SenTestingKit.framwork in /Library/Frameworks and dragged it into the Linked Frameworks folder inside of the External Frameworks folder. I unchecked the "CheckbookX" target and instead checked the "Testing" target.
  7. Just to make sure that everything was fine so far, I recompiled and ran CheckbooX.
  8. Built the new testing target and verified that nothing happens. That's because we haven't written any test code yet.

Pages: 1, 2

Next Pagearrow