Почему мои тесты OCUnit терпят неудачу с кодом 138? - PullRequest
10 голосов
/ 09 июля 2009

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

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

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

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

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

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 Ответ

15 голосов
/ 10 июля 2009

Я сталкивался с этим много раз сам, и это всегда раздражает. По сути, это обычно означает, что ваши юнит-тесты сделали сбой, но это не помогает изолировать ошибку. Если модульные тесты выдавали выходные данные до сбоя (откройте «Сборка»> «Результаты сборки»), вы обычно, по крайней мере, можете получить представление о том, какой тест выполнялся при возникновении проблемы, но это само по себе обычно не слишком полезно.

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

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

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