Подтвердить что ты не робот

Генерировать gcda файлы с Xcode5, iOS7-симулятором и XCTest

Будучи вдохновленным решением по этому вопросу, я попытался использовать тот же подход с XCTest.

Я установил "Generate Test Coverage Files = YES" и "Flow Program Program = YES".

XCode по-прежнему не создает никаких файлов gcda. У кого-нибудь есть идеи, как это решить?

код:

#import <XCTest/XCTestLog.h>

@interface VATestObserver : XCTestLog

@end

static id mainSuite = nil;

@implementation VATestObserver

+ (void)initialize {
    [[NSUserDefaults standardUserDefaults] setValue:@"VATestObserver"
                                             forKey:XCTestObserverClassKey];
    [super initialize];
}

- (void)testSuiteDidStart:(XCTestRun *)testRun {
    [super testSuiteDidStart:testRun];

    XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
    [suite addTestRun:testRun];

    if (mainSuite == nil) {
        mainSuite = suite;
    }
}

- (void)testSuiteDidStop:(XCTestRun *)testRun {
    [super testSuiteDidStop:testRun];

    XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
    [suite addTestRun:testRun];

    if (mainSuite == suite) {
        UIApplication* application = [UIApplication sharedApplication];
        [application.delegate applicationWillTerminate:application];
    }
}

@end

В AppDelegate.m У меня есть:

extern void __gcov_flush(void);
- (void)applicationWillTerminate:(UIApplication *)application {
    __gcov_flush();
}

EDIT: я отредактировал вопрос, чтобы отразить текущий статус (без красных сельдей).

EDIT Чтобы сделать это, мне пришлось добавить все тестируемые файлы к тестовой цели, включая VATestObserver.

AppDelegate.m

#ifdef DEBUG
+ (void)initialize {
    if([self class] == [AppDelegate class]) {
        [[NSUserDefaults standardUserDefaults] setValue:@"VATestObserver"
                                                 forKey:@"XCTestObserverClass"];
    }
}
#endif

VATestObserver.m

#import <XCTest/XCTestLog.h>
#import <XCTest/XCTestSuiteRun.h>
#import <XCTest/XCTest.h>

// Workaround for XCode 5 bug where __gcov_flush is not called properly when Test Coverage flags are set

@interface VATestObserver : XCTestLog
@end

#ifdef DEBUG
extern void __gcov_flush(void);
#endif

static NSUInteger sTestCounter = 0;
static id mainSuite = nil;

@implementation VATestObserver

+ (void)initialize {
    [[NSUserDefaults standardUserDefaults] setValue:@"VATestObserver"
                                             forKey:XCTestObserverClassKey];
    [super initialize];
}

- (void)testSuiteDidStart:(XCTestRun *)testRun {
    [super testSuiteDidStart:testRun];

    XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
    [suite addTestRun:testRun];

    sTestCounter++;

    if (mainSuite == nil) {
        mainSuite = suite;
    }
}

- (void)testSuiteDidStop:(XCTestRun *)testRun {

    sTestCounter--;

    [super testSuiteDidStop:testRun];

    XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
    [suite addTestRun:testRun];

    if (sTestCounter == 0) {
        __gcov_flush();
    }
}
4b9b3361

Ответ 1

Обновление 1:

Прочитав немного больше об этом, мне стало ясно 2 вещи (выделено мной):

Тесты и тестируемое приложение скомпилируются отдельно. Тесты фактически вводятся в запущенное приложение, поэтому __gcov_flush() необходимо вызвать внутри приложения, не входящего в теги.

- Покрытие кода Xcode5 (из cmd-строки для сборки CI) - Переполнение стека

и

Опять же: инъекция сложна. Ваш урон должен быть: Не добавляйте файлы .m из вашего приложения в тестовый объект. Вы получите неожиданное поведение.

- Контрольные контроллеры просмотра - # 1 - Контроллеры с подсветкой

Код ниже был изменен, чтобы отразить эти две идеи...


Обновление 2:

Добавлена ​​информация о том, как сделать эту работу для статических библиотек по запросу @MdaG в комментариях. Основные изменения для библиотек:

  • Мы можем скрыться непосредственно из метода -stopObserving, потому что нет отдельного приложения, где нужно вводить тесты.

  • Мы должны зарегистрировать наблюдателя в методе +load, потому что к моменту вызова +initialize (когда класс сначала получает доступ из набора тестов) уже слишком поздно, чтобы XCTest его забирал.


Решение

Другие ответы здесь очень помогли мне в создании кода в моем проекте. Изучая их, я считаю, что мне удалось немного упростить код для исправления.

Учитывая одно из:

  • ExampleApp.xcodeproj создан с нуля как "Пустое приложение"
  • ExampleLibrary.xcodeproj создается как независимая "Cocoa сенсорная статическая библиотека"

Это были шаги, которые я предпринял для включения генерации кода в Xcode 5:

  • Создайте файл GcovTestObserver.m со следующим кодом внутри группы ExampleAppTests:

    #import <XCTest/XCTestObserver.h>
    
    @interface GcovTestObserver : XCTestObserver
    @end
    
    @implementation GcovTestObserver
    
    - (void)stopObserving
    {
        [super stopObserving];
        UIApplication* application = [UIApplication sharedApplication];
        [application.delegate applicationWillTerminate:application];
    }
    
    @end
    

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

    #import <XCTest/XCTestObserver.h>
    
    @interface GcovTestObserver : XCTestObserver
    @end
    
    @implementation GcovTestObserver
    
    - (void)stopObserving
    {
        [super stopObserving];
        extern void __gcov_flush(void);
        __gcov_flush();
    }
    
    @end
    
  • Чтобы зарегистрировать класс тестового наблюдателя, добавьте следующий код в раздел @implementation любого из следующих:

    • ExampleAppDelegate.m, внутри группы ExampleApp
    • ExampleLibrary.m, внутри группы ExampleLibrary

     

    #ifdef DEBUG
    + (void)load {
        [[NSUserDefaults standardUserDefaults] setValue:@"XCTestLog,GcovTestObserver"
                                                 forKey:@"XCTestObserverClass"];
    }
    #endif
    

    Ранее этот ответ предложил использовать метод +initialize (и вы все еще можете это сделать в случае приложений), но он не работает для библиотек...

    В случае библиотеки +initialize, вероятно, будет выполняться только тогда, когда тесты впервые вызовут код библиотеки, и к тому времени уже слишком поздно регистрировать наблюдателя. Используя метод +load, регистрация наблюдателя всегда выполняется вовремя, независимо от того, какой сценарий.

  • В случае приложений добавьте следующий код в раздел @implementation файла ExampleAppDelegate.m внутри группы ExampleApp, чтобы очистить файлы покрытия при выходе из приложения

    - (void)applicationWillTerminate:(UIApplication *)application
    {
    #ifdef DEBUG
        extern void __gcov_flush(void);
        __gcov_flush();
    #endif
    }
    
  • Включите Generate Test Coverage Files и Instrument Program Flow, установив их в YES в настройках сборки проекта (для целей "Пример" и "Пример тестов" ).

    Чтобы сделать это легко и последовательно, я добавил файл Debug.xcconfig связанный с конфигурацией проекта "Отладка" , со следующими объявлениями:

    GCC_GENERATE_TEST_COVERAGE_FILES = YES
    GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES
    
  • Убедитесь, что все файлы проекта .m также включены в фазу сборки "Источники компиляции" целевой цели "Примеры тестов". Не делайте этого: код приложения принадлежит к целевому приложению, тестовый код принадлежит тестовой цели!

После запуска тестов для вашего проекта вы сможете найти созданные файлы покрытия для Example.xcodeproj здесь:

cd ~/Library/Developer/Xcode/DerivedData/
find ./Example-* -name *.gcda

Примечания

Шаг 1

Объявление метода внутри XCTestObserver.h указывает:

/*! Sent immediately after running tests to inform the observer that it time 
    to stop observing test progress. Subclasses can override this method, but 
    they must invoke super implementation. */
- (void) stopObserving;

Шаг 2

2.а)

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

Объявление константного ключа внутри XCTestObserver.h предлагает только следующее:

/*! Setting the XCTestObserverClass user default to the name of a subclass of 
    XCTestObserver indicates that XCTest should use that subclass for reporting 
    test results rather than the default, XCTestLog. You can specify multiple 
    subclasses of XCTestObserver by specifying a comma between each one, for 
    example @"XCTestLog,FooObserver". */
XCT_EXPORT NSString * const XCTestObserverClassKey;

2б)

Несмотря на то, что обычная практика использования if(self == [ExampleAppDelegate class]) вокруг кода внутри +initialize [Примечание: теперь он использует +load], мне легче опустить его в этом конкретном случае: не нужно настраивать на правильную имя класса при копировании и вставке.

Кроме того, защита от запуска кода дважды не нужна здесь: это не включено в сборку релизов, и даже если мы подклассом ExampleAppDelegate нет проблем при запуске этого кода более чем на один.

2.c)

В случае библиотек первый намек на проблему возник из этого комментария кода в Google Toolbox для Mac проект: GTMCodeCovereageApp.m

+ (void)load {
  // Using defines and strings so that we don't have to link in XCTest here.
  // Must set defaults here. If we set them in XCTest we are too late
  // for the observer registration.
  // (...)

И поскольку Ссылка на NSObject Class Reference указывает:

initialize. Инициализирует класс, прежде чем он получит свое первое сообщение.

 

load - вызывается всякий раз, когда класс или категория добавляются в среду Objective-C

Проект "EmptyLibrary"

Если кто-то пытается воспроизвести этот процесс, создав свой собственный проект "EmptyLibrary", помните, что вам нужно как-то вызвать код библиотеки из стандартных emtpy.

Если основной класс библиотеки не вызывается из тестов, компилятор попытается быть умным, и он не добавит его во время выполнения (поскольку он не вызван нигде), поэтому метод +load получить вызов.

Вы можете просто вызвать какой-то безобидный метод (как Apple предлагает в своих Руководства по кодированию для Cocoa # Инициализация класса). Например:

- (void)testExample
{
    [ExampleLibrary self];
}

Ответ 2

Поскольку вам нужно создать новый экземпляр XCTestSuiteRun в методе testSuiteDidStop, вы не получите правильные результаты при проверке ==. Вместо того, чтобы зависеть от равенства экземпляра, мы использовали простой счетчик и сбросили вызов, когда он достиг нулевого значения, что будет, когда заканчивается выполнение XCTestSuite верхнего уровня. Вероятно, есть более умные способы сделать это.

Во-первых, нам нужно было установить "Создавать файлы тестового покрытия = ДА" и "Инструментальный программный поток = ДА" в и цели тестирования и основного приложения.

#import <XCTest/XCTestLog.h>
#import <XCTest/XCTestSuiteRun.h>
#import <XCTest/XCTest.h>

// Workaround for XCode 5 bug where __gcov_flush is not called properly when Test Coverage flags are set

@interface GCovrTestObserver : XCTestLog
@end

#ifdef DEBUG
extern void __gcov_flush(void);
#endif

static NSUInteger sTestCounter = 0;
static id mainSuite = nil;

@implementation GCovrTestObserver

- (void)testSuiteDidStart:(XCTestRun *)testRun {
    [super testSuiteDidStart:testRun];

    XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
    [suite addTestRun:testRun];

    sTestCounter++;

    if (mainSuite == nil) {
        mainSuite = suite;
    }
}

- (void)testSuiteDidStop:(XCTestRun *)testRun {

    sTestCounter--;

    [super testSuiteDidStop:testRun];

    XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
    [suite addTestRun:testRun];

    if (sTestCounter == 0) {
        __gcov_flush();
    }
}

@end

Необходим дополнительный шаг, потому что вызов + инициализации не выполнялся на наблюдателе при включении в тестовый объект.

В AppDelegate добавьте следующее:

#ifdef DEBUG
+(void) initialize {
    if([self class] == [AppDelegate class]) {
        [[NSUserDefaults standardUserDefaults] setValue:@"GCovrTestObserver"
                                                 forKey:@"XCTestObserverClass"];
    }
}
#endif

Ответ 3

Здесь другое решение, которое позволяет избежать необходимости редактировать AppDelegate

UIApplication + Instrumented.m(поместите это в свою основную цель):

@implementation UIApplication (Instrumented)

#ifdef DEBUG

+ (void)load
{
    NSString* key = @"XCTestObserverClass";
    NSString* observers = [[NSUserDefaults standardUserDefaults] stringForKey:key];
    observers = [NSString stringWithFormat:@"%@,%@", observers, @"XCTCoverageFlusher"];
    [[NSUserDefaults standardUserDefaults] setValue:observers forKey:key];
}

- (void)xtc_gcov_flush
{
    extern void __gcov_flush(void);
    __gcov_flush();
}

#endif

@end

XCTCoverageFlusher.m(поместите это в свою тестовую цель):

@interface XCTCoverageFlusher : XCTestObserver
@end

@implementation XCTCoverageFlusher

- (void) stopObserving
{
    [super stopObserving];
    UIApplication* application = [UIApplication sharedApplication];
    SEL coverageFlusher = @selector(xtc_gcov_flush);
    if ([application respondsToSelector:coverageFlusher])
    {
        objc_msgSend(application, coverageFlusher);
    }
    [application.delegate applicationWillTerminate:application];
}

@end

Ответ 4

- (void)applicationWillTerminate:(UIApplication*)application должен быть определен в делетете приложения, а не в классе наблюдателя.

У меня не было проблем с библиотекой. "-lgov" не требуется, и вам не нужно добавлять какие-либо библиотеки. Охват поддерживается непосредственно компилятором LLVM.

Ответ 5

Процесс для этого немного отличается, если вы используете Specta, так как он делает свой собственный swizzling. Для меня работает следующее:

тестовый комплект:

@interface MyReporter : SPTNestedReporter // keeps the default reporter style
@end

@implementation MyReporter

- (void) stopObserving
{
  [super stopObserving];
  UIApplication* application = [UIApplication sharedApplication];
  [application.delegate applicationWillTerminate:application];
}

@end

AppDelegate:

- (void)applicationWillTerminate:(UIApplication *)application
{
#ifdef DEBUG
  extern void __gcov_flush(void);
  __gcov_flush();
#endif
}

Затем вам нужно включить собственный пользовательский подкласс, установив переменную окружения SPECTA_REPORTER_CLASS в MyReporter в разделе "Выполнить" вашей основной схемы.

Ответ 6

GCOV Flush in - (void) applicationWillTerminate не работает для меня, я думаю, потому что мое приложение работает в фоновом режиме.

Я также установил 'Generate Test Coverage Files = YES' и 'Instrument Program Flow = YES', но не gcda-Files.

Затем я выполнил "__gcov_flush()" in - (void) tearDown из TestClass, который дал мне gcda-Files для моего TestClass;)

Затем я создал следующую функцию в приложении AppDelegate:

@interface AppDelegate : UIResponder <UIApplicationDelegate>
+(void)gcovFlush;
@end

@implementation AppDelegate
+(void)gcovFlush{
  extern void __gcov_flush(void);
  __gcov_flush();
  NSLog(@"%s - GCOV FLUSH!", __PRETTY_FUNCTION__);
}
@end

Я назвал [AppDelegate gcovFlush] в моем - (void) tearDown и voilá, есть мои файлы gcda;)

Я надеюсь, что это поможет, пока Крис