使用Xcode5,iOS7模拟器和XCTest生成gcda文件

Mda*_*daG 33 code-coverage ios7 xcode5

受到这个问题解决方案的启发,我尝试使用与XCTest相同的方法.

我设置了'Generate Test Coverage Files = YES'和'Instrument Program Flow = 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
Run Code Online (Sandbox Code Playgroud)

在AppDelegate.m我有:

extern void __gcov_flush(void);
- (void)applicationWillTerminate:(UIApplication *)application {
    __gcov_flush();
}
Run Code Online (Sandbox Code Playgroud)

编辑:我编辑了问题以反映当前状态(没有红色鲱鱼).

编辑为了使其工作,我必须将测试中的所有文件添加到测试目标,包括VATestObserver.

AppDelegate.m

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

Hug*_*ira 44

更新1:

在详细了解了这一点之后,我现在已经清楚了两件事情(重点补充):

测试和测试的应用程序是单独编译的.测试实际上是注入到正在运行的应用程序中,因此__gcov_flush()必须在应用程序内部而不是在测试内部调用.

- Xcode5代码覆盖率(来自CI构建的cmd-line) - Stack Overflow

和,

再说一次:注射很复杂.您的带走应该是:不要将.m文件从您的应用程序添加到测试目标.你会得到意想不到的行为.

- 测试视图控制器 - #1 - 更轻的视图控制器

以下代码已更改,以反映这两个见解......


更新2:

根据评论中@MdaG的要求,添加了有关如何使静态库工作的信息.图书馆的主要变化是:

  • 我们可以直接从-stopObserving方法中刷新,因为没有单独的应用程序在哪里注入测试.

  • 我们必须在+load方法中注册观察者,因为在+initialize调用时(从测试套件首次访问类时),XCTest已经为时已晚.


这里的其他答案对我在项目中设置代码覆盖率有很大帮助.在探索它们时,我相信我已经设法简化了修复程序的代码.

考虑以下任何一个:

  • ExampleApp.xcodeproj 从头开始创建"空应用程序"
  • ExampleLibrary.xcodeproj 创建为独立的"可可触摸静态库"

这些是我在Xcode 5中启用代码覆盖率生成的步骤:

  1. GcovTestObserver.mExampleAppTests组中使用以下代码创建文件:

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

    在执行库时,由于没有应用程序可以调用,因此可以直接从观察者调用flush.在这种情况下,请使用以下代码将文件添加到ExampleLibraryTests组:

    #import <XCTest/XCTestObserver.h>
    
    @interface GcovTestObserver : XCTestObserver
    @end
    
    @implementation GcovTestObserver
    
    - (void)stopObserving
    {
        [super stopObserving];
        extern void __gcov_flush(void);
        __gcov_flush();
    }
    
    @end
    
    Run Code Online (Sandbox Code Playgroud)
  2. 要注册测试观察者类,请将以下代码添加到以下@implementation任一部分:

    • ExampleAppDelegate.m文件,在ExampleApp组内
    • ExampleLibrary.m文件,在ExampleLibrary组内

     

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

    以前,这个答案建议使用这种+initialize方法(你仍然可以在应用程序的情况下这样做),但它不适用于库......

    对于库,+initialize只有在测试第一次调用库代码时才会执行,然后注册观察者已经太晚了.使用该+load方法,观察者注册总是及时完成,无论哪种情况.

  3. 对于Apps,请将以下代码添加到ExampleApp组内的文件@implementation部分,以在退出应用程序时刷新coverage文件:ExampleAppDelegate.m

    - (void)applicationWillTerminate:(UIApplication *)application
    {
    #ifdef DEBUG
        extern void __gcov_flush(void);
        __gcov_flush();
    #endif
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 启用Generate Test Coverage Files并在项目构建设置中Instrument Program Flow设置它们YES(对于"示例"和"示例测试"目标).

    为了以简单一致的方式执行此操作,我添加了与项目的"Debug"配置相关联Debug.xcconfig文件,其中包含以下声明:

    GCC_GENERATE_TEST_COVERAGE_FILES = YES
    GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES
    
    Run Code Online (Sandbox Code Playgroud)
  5. 确保所有项目的.m文件也包含在"示例测试"目标的"编译源"构建阶段中. 不要这样做:app代码属于app目标,测试代码属于测试目标!

在为项目运行测试之后,您将能够Example.xcodeproj在此处找到生成的覆盖文件:

cd ~/Library/Developer/Xcode/DerivedData/
find ./Example-* -name *.gcda
Run Code Online (Sandbox Code Playgroud)

笔记

步骤1

里面的方法声明XCTestObserver.h表明:

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

第2步

2.A)

通过创建和注册单独的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;
Run Code Online (Sandbox Code Playgroud)

2.B)

尽管常见的做法是使用[注意:它现在正在使用]中if(self == [ExampleAppDelegate class])的代码,我发现在这种特殊情况下更容易省略它:在复制和粘贴时无需调整到正确的类名.+initialize +load

此外,防止运行代码两次的保护在这里并不是必需的:这不包含在发布版本中,即使我们子类ExampleAppDelegate,运行此代码也没有问题.

2.C)

就图书馆而言,问题的第一个提示来自Google Toolbox for 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.
  // (...)
Run Code Online (Sandbox Code Playgroud)

并且正如NSObject类参考指示:

initialize - 在收到第一条消息之前初始化类

load - 每当将类或类别添加到Objective-C运行时时调用

"EmptyLibrary"项目

如果有人试图通过创建自己的"EmptyLibrary"项目来复制此过程,请记住,您需要以某种方式从默认的emtpy测试中调用库代码.

如果未从测试中调用主库类,则编译器将尝试智能并且不会将其添加到运行时(因为它不会在任何地方调用),因此+load不会调用该方法.

您可以简单地调用一些无害的方法(正如Apple在他们的Cocoa #Class Initialization编码指南中所建议的那样).例如:

- (void)testExample
{
    [ExampleLibrary self];
}
Run Code Online (Sandbox Code Playgroud)

  • 对我不起作用:由于app委托中的__gcov_flush(),我有一个链接错误.我试图将-lgcov标志添加到"其他链接器标志",但它也没有帮助.任何的想法? (3认同)
  • @KamilPyc奇怪......如果我从用户默认值中删除"XCTestLog"类,我根本就没有日志.也许你的"GcovTestObserver"是子类"XCTestLog",就像在其他答案和问题中完成的一样?这可以解释重复的日志.对于*这个*答案的代码,您需要继承子类"XCTestObserver". (2认同)