10

現在、XCode 3.1 を使用して Objective-C を学習しようとしています。私は小さなプログラムに取り組んでおり、それに単体テストを追加することにしました。

Apple Developer ページの手順に従いました - Automated Unit Testing with Xcode 3 and Objective-C。最初のテストを追加したとき、テストが失敗したときは問題なく動作しましたが、テストを修正するとビルドが失敗しました。Xcode は次のエラーを報告しました:

エラー: テスト ホスト '/Users/joe/Desktop/OCT/build/Debug/OCT.app/Contents/MacOS/OCT' がコード 138 で異常終了しました (クラッシュした可能性があります)。

エラーを特定しようとして、上記の単体テストの例の手順を再度実行したところ、例が機能しました。コードの簡易バージョンとテスト ケースを追加すると、エラーが返されました。

作成したコードは次のとおりです。

カード.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

カード.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
4

1 に答える 1

15

私はこれに何度も遭遇しましたが、いつも迷惑です。基本的に、これは通常、単体テストクラッシュしたことを意味しますが、エラーの特定には役立ちません。単体テストがクラッシュする前に出力を生成した場合 ([ビルド] > [ビルド結果] を開きます)、問題が発生したときに実行されていたテストを少なくとも知ることができますが、通常、これだけではあまり役に立ちません。

原因を追跡するための最も一般的な提案は、単体テストをデバッグすることです。OCUnit を使用する場合、残念ながら、これは [実行] > [デバッグ] を選択するよりも複雑です。ただし、使用している同じチュートリアルには、「OCUnit でデバッガーを使用する」というタイトルのセクションが下部にあり、Xcode でカスタム実行可能ファイルを作成して、デバッガーがアタッチできる方法で単体テストを実行する方法を説明しています。そうすると、デバッガーは、すべてが炎上したときに謎の「コード 138」を取得する代わりに、エラーが発生した場所で停止します。

エラーの原因を正確に推測することはできないかもしれませんが、いくつかの提案があります...

  • init メソッドでの自動解放selfは絶対に行わないでください — メモリの保持と解放の規則に違反します。オブジェクトが予期せず解放された場合、それだけでクラッシュが発生します。たとえば、あなたのtestInitByIndexメソッドでは、 testCardautoreleased が返されます — したがって、[testCard release]最後の行で == クラッシュが保証されます。
  • initByIndex:メソッドの名前を に変更するinitWithIndex:か、 に切り替えて、処理する必要がある単一の値(または< 0 のテストを排除する )initWithSuit:(int)suit rank:(int)rankの代わりに、両方の値を渡すことができるようにすることをお勧めします。intNSUInteger
  • 自動解放されたオブジェクトを返すメソッドが本当に必要な場合は、+(Card*)cardWithSuit:(int)suit rank:(int)rank代わりに次のような便利なメソッドを作成することもできます。このメソッドは、1 行の alloc/init/autorelease の組み合わせの結果を返すだけです。
  • (マイナー) デバッグが完了したら、deallocsuper を呼び出すだけの を削除します。割り当てが解除されていないメモリを見つけようとしている場合は、Instruments を使用する方がはるかに簡単に見つけることができます。
  • (ニグル) テスト方法については、STAssetEquals(testCard.rank, 0, ...)代わりに使用することを検討してください。同じことをテストしますが、結果のエラーは少し理解しやすいです。
  • (自明) で単体テスト メソッドを宣言する必要はありません@interface。OCUnit は、任意の形式のメソッドを動的に実行-(void)test...します。それらを宣言しても害はありませんが、それらを省略すれば入力の手間を省くことができます。関連する注意事項として、私は通常、単体テスト用の .m ファイルのみを持ち、そのファイルの先頭に @interface セクションを配置します。他の誰も私の単体テスト インターフェイスを含める必要がないため、これはうまく機能します。
  • (単純さ) をサブクラス化しない限りCardTestCases、.h ファイルを削除し、代わりに @interface を .m ファイルの先頭に配置する方が簡単です。複数のファイルに宣言を含める必要がある場合はヘッダー ファイルが必要ですが、これは通常、単体テストには当てはまりません。

これらの提案を含むテスト ファイルは次のようになります。

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
于 2009-07-09T21:03:49.843 に答える