使用Xcode5,iOS7模拟器和XCTest生成gcda文件 [英] Generate gcda-files with Xcode5, iOS7 simulator and XCTest

查看:145
本文介绍了使用Xcode5,iOS7模拟器和XCTest生成gcda文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

受到此问题解决方案的启发我尝试使用与XCTest相同的方法。



我设置了'生成测试覆盖率文件=是'和'仪器程序流程=是'。



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

In AppDelegate.m我有:

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

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



编辑为了使其工作,我必须将测试中的所有文件添加到测试目标包括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>

// XCode 5错误的解决方法当设置测试覆盖率标志时,__gcov_flush未正确调用

@interface VATestObserver:XCTestLog
@end

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

静态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;
}
}

- (无效)testSuiteDidStop:(XCTestRun *)testRun {

sTestCounter--;

[super testSuiteDidStop:testRun];

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

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


解决方案

更新1:



在阅读了更多相关信息之后,我现在已经清楚了两件事(强调补充):


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

- Xcode5代码覆盖率(来自cmd-line for CI版本) - Stack Overflow



再次:注射很复杂。您的带走应该是:不要将.m文件从您的应用添加到测试目标。您将获得意外行为。



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


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






更新2:



添加了有关如何使静态库工作的信息,如<评论中有一个href =https://stackoverflow.com/users/351696/mdag> @MdaG 。库的主要变化是:




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


  • 我们必须在中注册观察者+ load 方法,因为当调用 + initialize 时(首次从测试套件访问类时),对于XCTest来说已经太晚了捡起来。







解决方案



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



考虑以下任何一个:




  • ExampleApp.xcodeproj 从头开始创建为空应用程序

  • ExampleLibrary.xcodeproj 创建为独立的Cocoa Touch静态库



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


  1. 创建 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

    在做图书馆时,从那以后没有应用程序可以调用,可以直接从观察者调用flush。在这种情况下,请使用以下代码将文件添加到 ExampleLibraryTests 组:

      #import< ; XCTest / XCTestObserver.h> 

    @interface GcovTestObserver:XCTestObserver
    @end

    @implementation GcovTestObserver

    - (void)stopObserving
    {
    [super stopObserving];
    extern void __gcov_flush(void);
    __gcov_flush();
    }

    @end


  2. 注册测试观察者类,将以下代码添加到以下任何一个的 @implementation 部分:




    • ExampleAppDelegate.m 文件,在 ExampleApp 组内

    • ExampleLibrary .m 文件,在 ExampleLibrary 组内



     

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

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



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


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

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


  4. 启用通过在项目构建中将它们设置为 YES 来生成测试覆盖率文件仪器程序流设置(对于示例和示例测试目标)。



    为了以简单一致的方式执行此操作,我添加了 Debug.xcconfig 文件相关联使用项目的调试配置,声明如下:

      GCC_GENERATE_TEST_COVERAGE_FILES = YES 
    GCC_INSTRUMENT_PROGRAM_FLOW_ARCS =是


  5. 确保所有项目的 .m 文件也包含在示例测试目标的编译源构建阶段。 不要这样做:app代码属于app目标,测试代码属于到测试目标!


运行后在你的项目测试中,你可以在这里找到 Example.xcodeproj 生成的覆盖文件:

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



注释



第1步



XCTestObserver.h 中的方法声明表示:

  / *!运行测试后立即发送,通知观察者停止观察测试进度的时间为
。子类可以覆盖此方法,但
它们必须调用super的实现。 * /
- (void)stopObserving;



第2步



2.a)



通过创建和注册单独的 XCTestObserver 子类,我们避免直接干扰默认 XCTestLog class。



XCTestObserver.h 中的常量键声明建议只是那:

  / *!将XCTestObserverClass用户默认设置为
XCTestObserver的子类名称表示XCTest应使用该子类报告
测试结果而不是默认的XCTestLog。您可以通过在每个
示例@XCTestLog,FooObserver之间指定逗号来指定XCTestObserver的多个
子类。 * /
XCT_EXPORT NSString * const XCTestObserverClassKey;



2.b)



尽管通常的做法是在 + initialize 内的代码周围使用 if(self == [ExampleAppDelegate class]) [注意:它现在使用 + load ] ,我发现在这种特殊情况下更容易省略它:在执行操作时无需调整到正确的类名副本&粘贴。



此外,这里不一定需要防止运行代码两次:这不包含在发布版本中,即使我们是子类 ExampleAppDelegate 运行此代码不会有多个问题。



2.c)



就图书馆而言,问题的第一个提示来自 Google Toolbox for Mac 项目: GTMCodeCovereageApp.m

  +(void)load { 
//使用定义和字符串,这样我们就不必在这里链接XCTest了。
//必须在此设置默认值。如果我们在XCTest中设置它们,那么观察者注册的时间太晚了
//。
//(...)

并且 NSObject类参考表示:


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







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



EmptyLibrary项目



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



如果主库类没有从测试中调用,编译器会尝试智能并且它不会添加i t到运行时(因为它没有在任何地方被调用),所以不会调用 + load 方法。



你可以简单地调用一些无害的方法(正如Apple建议的那样 Cocoa的编码指南#类初始化)。例如:

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


Being inspired by the solution to this question I tried using the same approach with XCTest.

I've set 'Generate Test Coverage Files=YES' and 'Instrument Program Flow=YES'.

XCode still doesn't produce any gcda files. Anyone have any ideas of how to solve this?

Code:

#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

In AppDelegate.m I have:

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

EDIT: I edited the question to reflect the current status (without the red herrings).

EDIT To make it work I had to add the all the files under test to the test target including 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();
    }
}

解决方案

Update 1:

After reading a bit more about this, 2 things have now become clear to me (emphasis added):

Tests and the tested application are compiled separately. Tests are actually injected into the running application, so the __gcov_flush() must be called inside the application not inside the tests.

Xcode5 Code Coverage (from cmd-line for CI builds) - Stack Overflow

and,

Again: Injection is complex. Your take away should be: Don’t add .m files from your app to your test target. You’ll get unexpected behavior.

Testing View Controllers – #1 – Lighter View Controllers

The code below was changed to reflect these two insights…


Update 2:

Added information on how to make this work for static libraries, as requested by @MdaG in the comments. The main changes for libraries is that:

  • We can flush directly from the -stopObserving method because there isn't a separate app where to inject the tests.

  • We must register the observer in the +load method because by the time the +initialize is called (when the class is first accessed from the test suite) it's already too late for XCTest to pick it up.


Solution

The other answers here have helped me immensely in setting up code coverage in my project. While exploring them, I believe I've managed to simplify the code for the fix quite a bit.

Considering either one of:

  • ExampleApp.xcodeproj created from scratch as an "Empty Application"
  • ExampleLibrary.xcodeproj created as an independent "Cocoa Touch Static Library"

These were the steps I took to enable Code Coverage generation in Xcode 5:

  1. Create the GcovTestObserver.m file with the following code, inside the ExampleAppTests group:

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

    When doing a library, since there is no app to call, the flush can be invoked directly from the observer. In that case, add the file to the ExampleLibraryTests group with this code instead:

    #import <XCTest/XCTestObserver.h>
    
    @interface GcovTestObserver : XCTestObserver
    @end
    
    @implementation GcovTestObserver
    
    - (void)stopObserving
    {
        [super stopObserving];
        extern void __gcov_flush(void);
        __gcov_flush();
    }
    
    @end
    

  2. To register the test observer class, add the following code to the @implementation section of either one of:

    • ExampleAppDelegate.m file, inside the ExampleApp group
    • ExampleLibrary.m file, inside the ExampleLibrary group

     

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

    Previously, this answer suggested to use the +initialize method (and you can still do that in case of Apps) but it doesn't work for libraries…

    In the case of a library, the +initialize will probably be executed only when the tests invoke the library code for the first time, and by then it's already too late to register the observer. Using the +load method, the observer registration in always done in time, regardless of which scenario.

  3. In the case of Apps, add the following code to the @implementation section of the ExampleAppDelegate.m file, inside the ExampleApp group, to flush the coverage files on exiting the app:

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

  4. Enable Generate Test Coverage Files and Instrument Program Flow by setting them to YES in the project build settings (for both the "Example" and "Example Tests" targets).

    To do this in an easy and consistent way, I've added a Debug.xcconfig file associated with the project's "Debug" configuration, with the following declarations:

    GCC_GENERATE_TEST_COVERAGE_FILES = YES
    GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES
    

  5. Make sure all the project's .m files are also included in the "Compile Sources" build phase of the "Example Tests" target. Don't do this: app code belongs to the app target, test code belongs to the test target!

After running the tests for your project, you'l be able to find the generated coverage files for the Example.xcodeproj in here:

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

Notes

Step 1

The method declaration inside XCTestObserver.h indicates:

/*! 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;

Step 2

2.a)

By creating and registering a separate XCTestObserver subclass, we avoid having to interfere directly with the default XCTestLog class.

The constant key declaration inside XCTestObserver.h suggests just that:

/*! 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.b)

Even though it's common practice to use if(self == [ExampleAppDelegate class]) around the code inside +initialize [Note: it's now using +load], I find it easier to omit it in this particular case: no need to adjust to the correct class name when doing copy & paste.

Also, the protection against running the code twice isn't really necessary here: this is not included in the release builds, and even if we subclass ExampleAppDelegate there is no problem in running this code more than one.

2.c)

In the case of libraries, the first hint of the problem came from this code comment in the Google Toolbox for Mac project: 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.
  // (...)

And as the NSObject Class Reference indicates:

initialize — Initializes the class before it receives its first message

load — Invoked whenever a class or category is added to the Objective-C runtime

The "EmptyLibrary" project

In case someone tries to replicate this process by creating their own "EmptyLibrary" project, please bear in mind that you need to invoke the library code from the default emtpy tests somehow.

If the main library class is not invoked from the tests, the compiler will try to be smart and it won't add it to the runtime (since it's not being called anywhere), so the +load method doesn't get called.

You can simply invoke some harmless method (as Apple suggests in their Coding Guidelines for Cocoa # Class Initialization). For example:

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

这篇关于使用Xcode5,iOS7模拟器和XCTest生成gcda文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆