Sunday, December 14, 2008

Unit Testing in XCode

With all previous applications I've written I have tried to implement a TDD (Test Development Development) process, as this leads to a more concise product which is also easier to support. With Microsoft Visual Studio I've tended to use NUnit rather than the built in option, however I've not seen anything out there for XCode. Recently I've come across a project in Google which does exactly the type of Unit Testing I was looking for. gives a number of simple steps for implementing TDD for use with the iPhone.

Here is another simple example.

Download the latest google-toolbox-for-mac file from the site. Extract the content of the ZIP file to your Hard Drive.

First we create an iPhone Window-Based Application, by choosing "File/New Project" from the xCode Menu. Click Choose and enter the name "Calculation" and click Save. This will be a simple non-functional harness that we can test against.


Add a simple object to the project by selecting "File/New" from the menu and selecting a NSObject subclass. Click Next and give it a filename of "Calculation" with a tick in the "Also create Calculation.h" box and click Finish.

Within Calculation.m we add a new method which takes an integer, add 5 to this and returns the result. The code would look like the following:
#import "Calculation.h"
@implementation Calculation
- (int) add5: (int) num
{
int result;
result = num 5;
return result;
}
@end



Within Calculation.h we the following:

#import <Foundation/Foundation.h>

@interface Calculation : NSObject {
}
- (int) add5: (int) num;
@end



Adding the Unit Test Framework


Next we add a UnitTest and supporting Framework to the project by adding a new target.

Right Click the Targets folder in XCode and select "Add/New Target" and select a Cococ Touch Application and give it the name "UnitTests", adding it to the Calculation project.


Create a new File Group within the XCode project by selecting "Project/New Group" from the menu and giving it the name UnitTests. Add the following files to the Group which are all part of the Google ToolBox for Mac ZIP file you extracted earlier. Be sure to set the target as UnitTets and not Calculation
  • GoogleToolsForMac/GTMDefines.h

  • GoogleToolsForMac/UnitTesting/GTMIPhoneUnitTestDelegate.h

  • GoogleToolsForMac/UnitTesting/GTMIPhoneUnitTestDelegate.m

  • GoogleToolsForMac/UnitTesting/GTMIPhoneUnitTestMain.m

  • GoogleToolsForMac/UnitTesting/GTMSenTestCase.h

  • GoogleToolsForMac/UnitTesting/GTMSenTestCase.m




Finally add a new Build Script to the project by right clicking the "UnitTests" Target and selecting "Add/New Build Phase/New Run Script Build Phase ".



The Shell should be "/bin/sh" and the Script should be the location of the "RunIPhoneUnitTest.sh" file located in the Google ToolBox for Mac ZIP file you extracted earlier. In my case it was:
/Volumes/Windows/Development/GoogleToolsForMac/UnitTesting/RuniPhoneUnitTest.sh


Change the Active Target in XCode to "UnitTests" by using the drop down box in the top left of the Project Window and click Build. Everything should compile without error. Now click "Build and Go" and the iPhone Simulator should start and the Debug Console will display a list of Unit Test messages ending with "Executed 0 tests, with 0 failures (0 unexpected) in 0.001 (0.001) seconds". This means that the Framework has been installed and is ready for our custom tests.

Adding a custom Unit Test


Create a new class in XCode, by right clicking the UnitTests group and selecting "Add/New File". Choose a Cocoa Objecttve-C test case class" from the list shown and call it "CalculationTest" making sure that the target is set to UnitTests and unticking the "Also create CalculationTest.h" box and click Finish.



The code for our class would be the following:
#import "GTMSenTestCase.h"
#import "Calculation.h"

@interface CalculationTest : GTMTestCase {
}
@end

@implementation CalculationTest
- (void) testCalculation
{
Calculation *calculation = [[Calculation alloc] init];
STAssertEquals(16, [calculation add5:12], @"Not Equal! %d", [calculation add5:12]);
[calculation release];
}
@end

Finally we need to add Calculation.m and Calculation.h to our new UnitTests target and the easiest way to do this is to select them in XCode and Drag them down to the "Targets/UnitTests/Compile Sources" folder.

Running the custom Unit Test


Clicking Build in Xcode should produce the following:



This is telling us that the UnitTest which called the add5 method was passed 12 and returned 17, which is correct, however our Assertion was expecting a 16, so the test failed. We correct this by editing CalculationTest.m and changing the line:
STAssertEquals(16, [calculation add5:12], @"Not Equal! %d", [calculation add5:12]);
with
STAssertEquals(17, [calculation add5:12], @"Not Equal! %d", [calculation add5:12]);

Following this change we recompile and everything builds correctly.



This is a fairly trivial example, but it shows the steps needed to create a TDD process using XCode.