GHUnit: Writing Custom Assert Macros
When I evaluated unit testing frameworks for iPhone development, One of the reasons why I chose GHUnit was that it has more sophisticated Assert Macros than other available frameworks. Despite this fact, there are still some Asserts that I missed, so I simply took the time to write my own.
Unlike test frameworks in the .NET or Java ecosystem, all Objective-C Frameworks provide preprocessor macros to realize assertions instead of providing a static class with Assert methods. A typical assert macro looks like the following:
#define GHAssertEquals(a1, a2, description, ...) \
do { \
@try {\
if (@encode(__typeof__(a1)) != @encode(__typeof__(a2))) { \
[self failWithException:[NSException ghu_failureInFile:@"Type mismatch"...]; \
} else { \
if (![a1encoded isEqualToValue:a2encoded]) { \
[self failWithException:[NSException ghu_failureInEqualityBetweenValue...]; \
} \
} \
} \
@catch (id anException) {\
[self failWithException:[NSException ghu_failureInRaise...]; \
}\
} while(0)
The body of the macro consists of a do{} while(false) loop, which is used to provide local scope for variables needed to implement the assertion. It is clear that code executed only once, even though a loop construct is used. The macro first checks necessary preconditions, in this case argument types. This is necessary due to the nature of a macro being a simple text substitution rather than a true method call that the compiler checks argument types for (that’s why I don’t like assertions being implemented as macros but would rather like to see assert methods). Next is the actual assertion. The type check and the actual assertion are both wrapped in a try{} catch(){} block, so any errors occurring in the macro code let the test fail also (a real macro would have a lot of code for preparing exception descriptions etc.).
I consider the GHUnit macros as a very useful set of primitve’s that can be combined to construct more complicated assertions:
#define GHFileAssertNotEmpty(file) \
do { \
GHAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:file], nil); \
NSString* written = [NSString stringWithContentsOfFile:file]; \
GHAssertNotNil(written, nil); \
GHAssertGreaterThan((int)[written length], 0, nil); \
} while (0)
Note that I don’t need to take care of all the nasty details that are needed to write a proper primitive macro as outlined above. The only disadvantage with a macro like the one above is localizing the failed assertion, as the exception thrown might not be directly obvious from the code using the macro. It is not a real disadvantage of the method itself but rather inherent to all macros. XCode right-click Jump to Definition comes to the rescue here.
