この質問の 解決策に触発されて 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();
}
}
更新1:
これについてもう少し読んだ後、2つのことが明らかになりました(強調が追加されました)。
テストとテスト済みアプリケーションは別々にコンパイルされます。テストは実際には実行中のアプリケーションに注入されるため、
__gcov_flush()
は、tests 内ではなく、アプリケーション内で呼び出す必要があります。
そして、
繰り返しますが、注入は複雑です。 .mファイルをアプリからテストターゲットに追加しないでください。予期しない動作が発生します。
以下のコードは、これら2つの洞察を反映するように変更されました…
更新2:
コメントで @ MdaG によって要求されるように、これを静的ライブラリで機能させる方法に関する情報を追加しました。ライブラリの主な変更点は次のとおりです。
テストを挿入する別のアプリがないため、_-stopObserving
_メソッドから直接フラッシュできます。
_+load
_メソッドがオブザーバーを登録する必要があるのは、_+initialize
_が呼び出されるとき(クラスが最初にテストスイートからアクセスされるとき)には、XCTestがそれを取得するには遅すぎるためです。
ここでの他の回答は、プロジェクトでコードカバレッジを設定するのに非常に役立ちました。それらを調査している間に、修正のためのコードをかなり単純化できたと私は信じています。
次のいずれかを検討します。
ExampleApp.xcodeproj
_「空のアプリケーション」としてゼロから作成ExampleLibrary.xcodeproj
_Xcode 5でコードカバレッジの生成を有効にするために行った手順は次のとおりです。
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
_
テストオブザーバークラスを登録するには、次のいずれかのいずれかの_@implementation
_セクションに次のコードを追加します。
ExampleAppDelegate.m
_ファイル、 ExampleApp グループ内ExampleLibrary.m
_ファイル、 ExampleLibrary グループ内_#ifdef DEBUG
+ (void)load {
[[NSUserDefaults standardUserDefaults] setValue:@"XCTestLog,GcovTestObserver"
forKey:@"XCTestObserverClass"];
}
#endif
_
以前は、この答えは_+initialize
_メソッドを使用することを提案しました(そして、Appsの場合でも引き続き使用できます)が、ライブラリでは機能しません…
ライブラリの場合、_+initialize
_は、テストが初めてライブラリコードを呼び出したときにのみ実行される可能性があり、その時点ですでにオブザーバーを登録するには遅すぎます。 _+load
_メソッドを使用すると、どのシナリオでも、オブザーバーの登録は常に時間内に行われます。
アプリの場合、 ExampleApp グループ内の_@implementation
_ファイルの_ExampleAppDelegate.m
_セクションに次のコードを追加して、終了時にカバレッジファイルをフラッシュしますアプリ:
_- (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
_
_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;
_
別の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;
_
_+initialize
_内のコードの周りでif(self == [ExampleAppDelegate class])
を使用するのは一般的な方法ですが、[注:現在_+load
_]を使用していますが、この特定のケースでは省略が簡単です。コピーと貼り付けを行うときに正しいクラス名に調整する必要はありません。
また、コードを2回実行することに対する保護はここでは実際には必要ありません。これはリリースビルドに含まれておらず、ExampleAppDelegate
をサブクラス化しても、このコードを複数回実行しても問題はありません。
ライブラリの場合、問題の最初のヒントは 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」プロジェクトを作成してこのプロセスを複製しようとする場合、デフォルトの emtpy テストからライブラリコードを呼び出す必要があることに注意してください。
メインライブラリクラスがテストから呼び出されない場合、コンパイラはsmartを試行し、ランタイムに追加しません(どこからも呼び出されないため)。そのため、_+load
_メソッドは呼び出されません。
(Appleが示唆するように Cocoaのコーディングガイドライン#クラスの初期化 のように)いくつかの無害なメソッドを呼び出すだけです。例:
_- (void)testExample
{
[ExampleLibrary self];
}
_
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
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
-(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ファイルがあります;)
これがお役に立てば幸いです、さようならクリス
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_CLASS
をMyReporter
に設定して、カスタムレポーターサブクラスを有効にする必要があります。
- (void)applicationWillTerminate:(UIApplication*)application
は、オブザーバークラスではなく、アプリケーションデリゲートで定義する必要があります。
ライブラリの問題はありませんでした。 「-lgov」は不要であり、ライブラリを追加する必要はありません。カバレッジはLLVMコンパイラによって直接サポートされます。