Почему мои тесты OCUnit терпят неудачу с "кодом 138"?

В настоящее время я пытаюсь изучить objective-c с помощью XCode 3.1. Я работал над небольшой программой и решил добавить к ней модульное тестирование.

Я выполнил шаги на странице Apple Developer - Автоматическое тестирование единицы измерения с помощью Xcode 3 и Objective-C. Когда я добавил свой первый тест, он отлично работал, когда тесты не удались, но когда я исправил тесты, сборка завершилась неудачно. Xcode сообщила о следующей ошибке:

Ошибка: тестовый хост '/Users/joe/Desktop/OCT/build/Debug/OCT.app/Contents/MacOS/OCT' вышел из строя с кодом 138 (возможно, он разбился).

Пытаясь изолировать мою ошибку, я повторил шаги из примера Unit Test выше, и этот пример сработал. Когда я добавил упрощенную версию своего кода и тестового примера, ошибка вернулась.

Вот код, который я создал:

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

Ответ 1

Я встречал это много раз сам, и это всегда раздражало. В основном, это обычно означает, что ваши тесты устройств сработали, но не помогают изолировать ошибку. Если тесты модуля выдают результат перед сбоем (open Build > Build Results), вы обычно можете получить представление о том, какой тест выполнялся при возникновении проблемы, но это само по себе не слишком полезно.

Лучшим общим предложением для отслеживания причины является отладить ваши модульные тесты. При использовании OCUnit это, к сожалению, сложнее, чем выбор Run > Debug. Тем не менее, в том же учебнике, который вы используете, есть раздел в нижней части под названием " Использование отладчика с OCUnit", в котором объясняется, как создать пользовательский исполняемый файл в Xcode для выполнения модульных тестов таким образом, чтобы отладчик может подключаться. Когда вы это сделаете, отладчик остановится там, где произошла ошибка, вместо того, чтобы получить загадочный "код 138", когда все идет пламенем.

Хотя я не могу точно угадать, что вызывает ошибку, у меня есть несколько предложений...

  • НИКОГДА, НИКОГДА не проверяйте autorelease self в методе init - это нарушает правила хранения с сохранением-освобождением. Это само по себе приведет к сбоям, если объект будет выпущен неожиданно. Например, в вашем методе testInitByIndex testCard возвращается автоматически, поэтому [testCard release] в последней строке == гарантируется сбой.
  • Я бы предложил переименовать ваш метод initByIndex: на initWithIndex: или даже переключиться на initWithSuit:(int)suit rank:(int)rank, чтобы вы могли передавать оба значения вместо одного int (или NSUInteger), что устранило бы тестирование для < 0), которые вы должны обрабатывать.
  • Если вам действительно нужен метод, который возвращает объект с автореализацией, вы также можете создать удобный метод, например +(Card*)cardWithSuit:(int)suit rank:(int)rank. Этот метод просто вернет результат однострочной комбинации alloc/init/autorelease.
  • (Незначительный) После того, как вы закончите отладку, избавьтесь от dealloc, который просто вызывает супер. Если вы пытаетесь найти память, которая никогда не освобождается, гораздо проще найти с помощью инструментов.
  • (Niggle). Для вашего метода тестирования используйте вместо этого STAssetEquals(testCard.rank, 0, ...). Он тестирует одно и то же, но любая результирующая ошибка немного понятна.
  • (Trivial) Вам не нужно объявлять методы unit test в @interface. OCUnit динамически запускает любой метод формата -(void)test... для вас. Это не помешает объявить их, но вы сэкономите себя на машинке, если просто опустите их. В связанной заметке у меня обычно есть только .m файл для модульных тестов и помещаем раздел @interface в начало этого файла. Это отлично работает, поскольку никто не должен включать мой интерфейс unit test.
  • (Простота) Если вы не подклассом 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