Introduction to Unit Testing with OCUnit

May 23rd, 2012

Filed under: Mac Development | 3 comments

In a previous post I covered how to setup OCUnit unit testing in Xcode 4. This post provides an introduction to writing unit tests with OCUnit, which is a unit testing framework for Objective-C code that comes with Xcode.

Unit Testing Classes

When writing unit tests with OCUnit, you will create one or more unit testing classes. How many classes you create is a matter of personal preference. I usually have one unit testing class for each class in my application. If you have a large application class, you could create multiple unit testing classes for that class. You could also have one class that contains all your unit tests.

An OCUnit unit testing class contains two files: a header file with a .h extension and an implementation file with a .m extension. A newly created class’s header file looks similar to the following:

@interface TrieUnitTest : SenTestCase
@end

The unit testing class, TrieUnitTest in my example, inherits from the SenTestCase class, which is OCUnit’s base class. If you want to add any data members to the unit testing class, add them between the @interface and @end tags. A common data member to add to a unit testing class is an object of the application class you’re testing.

You generally won’t do much in the header file. In an Objective-C application you must declare a class’s methods in the header file.

- (void)DoSomething;

You do not need to declare your unit testing methods in the header file. Write your unit testing methods in the implementation file.

What appears in a newly created class’s implementation file depends on the version of Xcode you’re using and how the class was created. If you add a new unit testing class to a project in Xcode 4.3.2, you get an empty implementation to fill in.

@implementation TrieUnitTest
@end

If you create a new Xcode project in Xcode 4.3.2 with unit testing support, the unit testing class Xcode supplies has three methods: setUp:, tearDown:, and testExample, which creates a failing test. I will cover the setUp: and tearDown: methods later in this article. You can delete the testExample method, or you can keep it initially to see if unit testing is working.

Writing Test Methods

An OCUnit test method has the following requirements:

  • It must start with the word test. OCUnit treats methods with the test prefix as test methods and runs them automatically.
  • It takes no arguments.
  • It returns void.

The following code shows an example of defining a test method:

- (void)testDoSomething
{

}

You do not need to define test methods in the header file. Write the method in the implementation file.

One thing to keep in mind when writing test methods is you have no control over the order in which OCUnit runs your tests. Each unit test you write should be self-contained and should not rely on the results of other tests.

OCUnit Assertions

Assertion macros are the heart of unit testing. They determine whether or not the test passes. OCUnit has the following assertion macros:

  • STAssertEqualObjects asserts two Cocoa objects are equal.
  • STAssertEqual asserts two variables of a primitive data type, such as integers, are equal.
  • STAssertEqualsWithAccuracy asserts two floating-point variables are equal. This assertion allows you to deal with small inaccuracies in floating-point arithmetic.
  • STAssertFalse asserts a condition is false.
  • STAssertFalseNoThrow asserts a condition is false and no exceptions are thrown.
  • STAssertNil asserts a pointer is nil.
  • STAssertNoThrow asserts no exceptions are thrown.
  • STAssertNoThrowSpecific asserts no exceptions of a specific class are thrown.
  • STAssertNoThrowSpecificNamed asserts a specific exception is not thrown.
  • STAssertNotNil asserts a pointer is not nil.
  • STAssertThrows asserts an exception is thrown.
  • STAssertThrowsSpecific asserts an exception of a specific class is thrown.
  • STAssertThrowsSpecificNamed asserts a specific exception is thrown.
  • STAssertTrue asserts a condition is true.
  • STAssertTrueNoThrow asserts a condition is true and no exceptions are thrown.

In most cases a unit test should have only one assertion. One assertion per test makes it easier to find where a failing test fails. 

Test Method Example

Now it’s time to write a unit test. I’m going to test that multiplying two negative integers yields a positive integer. Here’s the test method:

- (void)testMultiplyingTwoNegativeIntegersYieldsPositiveProduct
{
	int x = -5;
	int y = -7;
	int product = x * y;

	STAssertTrue((product > 0), @"The product was less than zero.");
}

The most interesting part of the test is the assertion. I use the STAssertTrue macro because I’m testing a condition, whether the product is positive. I pass two arguments to the STAssertTrue macro. The first argument is the condition to test. The second argument is the error message OCUnit displays if the test fails. The @ before the first quote specifies that the error message is an Objective-C string.

If you add this test to one of your unit testing classes, it should pass. If you want to see what happens when a test fails, make x or y positive, change the condition in the assertion, or change STAssertTrue to STAssertFalse.

Improving the Error Message

The test I wrote in the last section shouldn’t fail, but if it does, the error message doesn’t help much. The error message tells you the product was less than zero, but it doesn’t tell you what the product’s value was, which is important to know. Let’s improve the error message.

STAssertTrue((product > 0), @"The product was less than zero. Product: %d", product);

Now if the test fails, it prints the value of the product variable. The %d in the error message is a format string that specifies printing a variable’s value as a decimal integer. If you have printed variables using NSLog or printf, you’re familiar with format strings. Use %f to print a floating-point variable. Use %s to print a string of characters.

You can print multiple variables by using multiple format strings and supplying the extra variables as arguments to the assertion. The following assertion tests that x and y are equal and prints their values if the test fails:

STAssertEquals(x, y, @"x and y were not equal. x: %d y: %d", x, y);

The setUp: and tearDown: Methods

Two optional methods that can assist you in unit testing are setUp: and tearDown:. If you have initialization code to perform before running multiple tests, you can place that code in the setUp: method to remove duplicate code in your tests. The tearDown: method contains cleanup code. A common case of using the tearDown: method is to free any memory you allocated in the setUp: method.

OCUnit calls the setUp: method before running each test and calls the tearDown: method after running each test. If your unit testing class has 20 tests, OCUnit calls setUp: and tearDown: 20 times.

The following example shows a minimal setUp: method:

- (void)setUp
{
	[super setUp];

	// Set-up code here.
}

Notice how you call the parent class’s setUp: method before writing your setup code.

The following example shows a minimal tearDown: method:

- (void)tearDown
	// Tear-down code here. 

	[super tearDown];
}

Notice how you call the parent class’s tearDown: method after writing your cleanup code.

 

Tags:


3 thoughts on “Introduction to Unit Testing with OCUnit

  1. Brinda says:

    Its really helpful to learn how to write test cases.
    But I want to know the deference between Logical unit testcases and application unit testcases.
    how do i write application unit test cases?
    could you please more elaborate with an example?

    • Mark Szymczyk says:

      Brinda,

      Xcode no longer distinguishes between logic and application unit tests so you don’t have to worry about the distinction. If you add a test case class to a project in Xcode 4.6, you’ll notice the only thing Xcode asks for is a class prefix. You can learn more in the following article:

      OCUnit Application vs. Logic Tests

      If you still want to know about the difference between logic and application test cases, the following Google search provided some explanations:

      xcode application vs logic unit tests

Leave a Reply

Your email address will not be published. Required fields are marked *