web-dev-qa-db-ja.com

Xcode5、iOS7シミュレーター、XCTestでgcdaファイルを生成する

この質問の 解決策に触発されて XCTestで同じアプローチを使用してみました。

「テストカバレッジファイルの生成= YES」および「機器プログラムフロー= 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();
    }
}
33
MdaG

更新1:

これについてもう少し読んだ後、2つのことが明らかになりました(強調が追加されました)。

テストとテスト済みアプリケーションは別々にコンパイルされます。テストは実際には実行中のアプリケーションに注入されるため、__gcov_flush()は、tests 内ではなく、アプリケーション内で呼び出す必要があります。

Xcode5コードカバレッジ(CIビルドのコマンドラインから)-スタックオーバーフロー

そして、

繰り返しますが、注入は複雑です。 .mファイルをアプリからテストターゲットに追加しないでください。予期しない動作が発生します。

View Controllersのテスト–#1 – Lighter View Controllers

以下のコードは、これら2つの洞察を反映するように変更されました…


更新2:

コメントで @ MdaG によって要求されるように、これを静的ライブラリで機能させる方法に関する情報を追加しました。ライブラリの主な変更点は次のとおりです。

  • テストを挿入する別のアプリがないため、_-stopObserving_メソッドから直接フラッシュできます。

  • _+load_メソッドがオブザーバーを登録する必要があるのは、_+initialize_が呼び出されるとき(クラスが最初にテストスイートからアクセスされるとき)には、XCTestがそれを取得するには遅すぎるためです。


解決

ここでの他の回答は、プロジェクトでコードカバレッジを設定するのに非常に役立ちました。それらを調査している間に、修正のためのコードをかなり単純化できたと私は信じています。

次のいずれかを検討します。

  • _ExampleApp.xcodeproj_「空のアプリケーション」としてゼロから作成
  • 独立した「Cocoa Touch Static Library」として作成された_ExampleLibrary.xcodeproj_

Xcode 5でコードカバレッジの生成を有効にするために行った手順は次のとおりです。

  1. ExampleAppTests グループ内に、次のコードで_GcovTestObserver.m_ファイルを作成します。

    _#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
    _
  2. テストオブザーバークラスを登録するには、次のいずれかのいずれかの_@implementation_セクションに次のコードを追加します。

    • _ExampleAppDelegate.m_ファイル、 ExampleApp グループ内
    • _ExampleLibrary.m_ファイル、 ExampleLibrary グループ内

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

    以前は、この答えは_+initialize_メソッドを使用することを提案しました(そして、Appsの場合でも引き続き使用できます)が、ライブラリでは機能しません…

    ライブラリの場合、_+initialize_は、テストが初めてライブラリコードを呼び出したときにのみ実行される可能性があり、その時点ですでにオブザーバーを登録するには遅すぎます。 _+load_メソッドを使用すると、どのシナリオでも、オブザーバーの登録は常に時間内に行われます。

  3. アプリの場合、 ExampleApp グループ内の_@implementation_ファイルの_ExampleAppDelegate.m_セクションに次のコードを追加して、終了時にカバレッジファイルをフラッシュしますアプリ:

    _- (void)applicationWillTerminate:(UIApplication *)application
    {
    #ifdef DEBUG
        extern void __gcov_flush(void);
        __gcov_flush();
    #endif
    }
    _
  4. プロジェクトのビルド設定で_Generate Test Coverage Files_と_Instrument Program Flow_をYESに設定して有効にします(「サンプル」と「サンプルテスト」の両方のターゲットの場合)。

    これを簡単かつ一貫した方法で行うために、次の宣言を含む_Debug.xcconfig_ファイル プロジェクトの「デバッグ」構成に関連付けられています を追加しました。

    _GCC_GENERATE_TEST_COVERAGE_FILES = YES
    GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES
    _
  5. プロジェクトのすべての_.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's time 
    to stop observing test progress. Subclasses can override this method, but 
    they must invoke super's implementation. */
- (void) stopObserving;
_

ステップ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;
_

2.b)

_+initialize_内のコードの周りでif(self == [ExampleAppDelegate class])を使用するのは一般的な方法ですが、[注:現在_+load_]を使用していますが、この特定のケースでは省略が簡単です。コピーと貼り付けを行うときに正しいクラス名に調整する必要はありません。

また、コードを2回実行することに対する保護はここでは実際には必要ありません。これはリリースビルドに含まれておらず、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.
  // (...)
_

そして NSObject Class Reference が示すように:

initialize —最初のメッセージを受信する前にクラスを初期化します

load —クラスまたはカテゴリがObjective-Cランタイムに追加されるたびに呼び出されます

「EmptyLibrary」プロジェクト

誰かが独自の「EmptyLibrary」プロジェクトを作成してこのプロセスを複製しようとする場合、デフォルトの emtpy テストからライブラリコードを呼び出す必要があることに注意してください。

メインライブラリクラスがテストから呼び出されない場合、コンパイラはsmartを試行し、ランタイムに追加しません(どこからも呼び出されないため)。そのため、_+load_メソッドは呼び出されません。

(Appleが示唆するように Cocoaのコーディングガイドライン#クラスの初期化 のように)いくつかの無害なメソッドを呼び出すだけです。例:

_- (void)testExample
{
    [ExampleLibrary self];
}
_
44
Hugo Ferreira

TestSuiteDidStopメソッドで新しいXCTestSuiteRunインスタンスを作成する必要があるため、==チェックで適切な結果が得られません。インスタンスの等価性に依存する代わりに、単純なカウンターを使用して、ゼロに達したときにフラッシュを呼び出します。これは、トップレベルのXCTestSuiteの実行が終了したときに行われます。これを行うには、おそらくもっと賢い方法があるでしょう。

最初に、「-Generate Test Coverage Files = YES」と「Instrument Program Flow = YES」をbothテストとメインアプリターゲットに設定する必要がありました。

#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

テストターゲットに含まれている場合、オブザーバーで+ initialize呼び出しが行われていないため、追加の手順が必要でした。

AppDelegateで、以下を追加します。

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

AppDelegateを編集する必要がない別のソリューションを次に示します

IApplication + 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
1
Jasper Blues

-(void)applicationWillTerminateのGCOVフラッシュは機能しませんでした。私のアプリがバックグラウンドで実行されているためです。

また、「Generate Test Coverage Files = YES」と「Instrument Program Flow = YES」を設定しましたが、gcda-Filesは設定していません。

次に、TestClassから-(void)tearDownで "__gcov_flush()"を実行し、TestClassのgcda-Filesを取得しました;)

次に、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

-(void)tearDownとvoiláで[AppDelegate gcovFlush]を呼び出しました。私のgcdaファイルがあります;)

これがお役に立てば幸いです、さようならクリス

0
sPooKee

Spectaを使用している場合、独自のスウィズリングを行うため、このプロセスは少し異なります。以下は私のために働いています:

テストバンドル:

@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
}

次に、メインスキームのRunセクションで環境変数SPECTA_REPORTER_CLASSMyReporterに設定して、カスタムレポーターサブクラスを有効にする必要があります。

0
Jonathan Crooke

- (void)applicationWillTerminate:(UIApplication*)applicationは、オブザーバークラスではなく、アプリケーションデリゲートで定義する必要があります。

ライブラリの問題はありませんでした。 「-lgov」は不要であり、ライブラリを追加する必要はありません。カバレッジはLLVMコンパイラによって直接サポートされます。

0
Sulthan