Under The Microscope

OCUnit/Xcode additions

OCUnitAs of late we’ve been progressively increasing our use of OCUnit, the unit testing system now bundled with Xcode. If you quickly browse through the OCUnit source code, you can see that it is incredible in both its flexibility and its complexity. Its integration into Xcode does a great job of masking all that complexity away, and leaves you with a very easy “Click Build and we’ll find, run, and report on your tests” option.This is great when you are just starting out and want to get something up and running. But naturally enough, when you mask away the complexity, the flexibility also goes with it. The biggest piece I’ve been looking to reclaim is just controlling which test cases get run. Currently, Xcode runs every single test, in a seemingly random order, every single time.When I am working on new code, I’m often interested in only one or two related test cases to the exclusion of all others and waiting for all the other tests to run before I get to the new ones quickly wears thin. Normally with OCUnit, you can control which test is run by passing the -SenTest argument with the name of a TestCase class to the otest. However given the way Xcode executes otest (see: /Developer/Tools/RunUnitTests), there is no way to get this argument through.Given this, I looked for a public API for modifying the set of tests at runtime, but there doesn’t appear to be any. Once otest is told to run all the tests, it’s going to run all the tests. Luckily though, we have one last resort. In otest, the method -[SenTestProbe specifiedTestSuite] determines which test cases to run. By overriding this method at runtime, we can generate our own TestSuite with just the tests we want to run.This can be done fairly easily, if we replace the SenTestProbe class with one of our own, using the slightly tricky but powerful poseAsClass method:@interface RATestProbe : SenTestProbe@end @implementation RATestProbe+ (void)load //Have to use a +load because +initialize will be called too late{[self poseAsClass: [SenTestProbe class]];}+ (SenTestSuite *)specifiedTestSuite{SenTestSuite* suite;#if RUN_ALL_TEST_CASESsuite = [super specifiedTestSuite];#elseNSString* path = [[NSBundle bundleForClass: [self class]] bundlePath];suite = [SenTestSuite emptyTestSuiteNamedFromPath: path];[suite addTest: [RAFooTests defaultTestSuite]];[suite addTest: [RABarTests defaultTestSuite]];//[suite addTest: [RAFooBarTests defaultTestSuite]];#endifreturn suite;}Now using poseAsClass isn’t the most elegant of solutions, but this being non-shipping unit test code we can be a bit more tolerant of the trickery. A nice side effect of this method is that we can easily preserve the original behavior of running all the test cases, by just calling up to SenTestProbe to get default test suite.Once you are programmatically determining which tests to run, you can do other neat things like reading the tests to run out of the test targets Info.plist file, or sorting the tests to run in a sane order and so on. This restores one piece of the original flexibility.

Leave a Reply

You must be logged in to post a comment.

Our Software