I'm currently trying to learn objective-c using XCode 3.1. I've been working on a small program and decided to add unit testing to it.
I followed the steps on the Apple Developer page - Automated Unit Testing with Xcode 3 and Objective-C. When I added my first test, it worked fine when the tests failed, but when I corrected the tests the build failed. Xcode reported the following error:
error: Test host '/Users/joe/Desktop/OCT/build/Debug/OCT.app/Contents/MacOS/OCT' exited abnormally with code 138 (it may have crashed).
Trying to isolate my error, I re-followed the steps from the Unit Test example above and the example worked. When I added a simplified version of my code and a test case, the error returned.
Here is the code I created:
Card.h
#import <Cocoa/Cocoa.h>
#import "CardConstants.h"
@interface Card : NSObject {
int rank;
int suit;
BOOL wild ;
}
@property int rank;
@property int suit;
@property BOOL wild;
- (id) initByIndex:(int) i;
@end
Card.m
#import "Card.h"
@implementation Card
@synthesize rank;
@synthesize suit;
@synthesize wild;
- (id) init {
if (self = [super init]) {
rank = JOKER;
suit = JOKER;
wild = false;
}
return [self autorelease];
}
- (id) initByIndex:(int) i {
if (self = [super init]) {
if (i > 51 || i < 0) {
rank = suit = JOKER;
} else {
rank = i % 13;
suit = i / 13;
}
wild = false;
}
return [self autorelease];
}
- (void) dealloc {
NSLog(@"Deallocing card");
[super dealloc];
}
@end
CardTestCases.h
#import <SenTestingKit/SenTestingKit.h>
@interface CardTestCases : SenTestCase {
}
- (void) testInitByIndex;
@end
CardTestCases.m
#import "CardTestCases.h"
#import "Card.h"
@implementation CardTestCases
- (void) testInitByIndex {
Card *testCard = [[Card alloc] initByIndex:13];
STAssertNotNil(testCard, @"Card not created successfully");
STAssertTrue(testCard.rank == 0,
@"Expected Rank:%d Created Rank:%d", 0, testCard.rank);
[testCard release];
}
@end
I've encountered this numerous times myself, and it's always annoying. Basically, it usually means that your unit tests did crash, but doesn't help isolate the error. If the unit tests produced output before crashing (open Build > Build Results) you can usually at least get an idea of what test was running when the problem occurred, but this alone usually isn't too helpful.
The best general suggestion for tracking down the cause is to debug your unit tests. When using OCUnit, this is unfortunately more complex than selecting Run > Debug. However, the same tutorial you're using has a section near the bottom titled "Using the Debugger with OCUnit" which explains how to create a custom executable in Xcode to execute your unit tests in a way that the debugger can attach to. When you do, the debugger will stop where the error occurred, instead of getting the mysterious "code 138" when everything goes down in flames.
Although I may not be able to guess exactly what's causing the error, I do have a few suggestions...
self
in an init method — it violates retain-release memory rules. That alone will lead to crashes if the object is released unexpectedly. For example, in your testInitByIndex
method, testCard
comes back autoreleased — therefore, [testCard release]
on the last line == guaranteed crash.initByIndex:
method to initWithIndex:
, or even switching to initWithSuit:(int)suit rank:(int)rank
so you can pass both values, instead of a single int
(or an NSUInteger
, which would eliminate testing for < 0) that you have to handle.+(Card*)cardWithSuit:(int)suit rank:(int)rank
instead. This method would just return the result of a one-line alloc/init/autorelease combination.dealloc
that just calls to super. If you're trying to find memory that's never deallocated, it's much easier to find using Instruments anyway.STAssetEquals(testCard.rank, 0, ...)
instead. It tests the same thing, but any resulting error is a bit easier to understand.@interface
. OCUnit dynamically runs any method of the format -(void)test...
for you. It doesn't hurt to declare them, but you'll save yourself some typing if you just omit them. On a related note, I usually have only a .m file for unit tests, and put the @interface section at the top of that file. This works great since nobody else needs to include my unit test interface.CardTestCases
, it is simpler to just eliminate the .h file and put the @interface at the top of the .m file instead. Header files are necessary when multiple files need to include the declarations, but this is usually not the case with unit tests.Here is what the test file might look like with these suggestions:
CardTest.m
#import <SenTestingKit/SenTestingKit.h>
#import "Card.h"
@interface CardTest : SenTestCase
@end
@implementation CardTest
- (void) testInitWithIndex {
Card *testCard = [[Card alloc] initWithIndex:13];
STAssertNotNil(testCard, @"Card not created successfully");
STAssertEquals(testCard.rank, 0, @"Unexpected card rank");
[testCard release];
}
@end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With