33 votes

Générer des fichiers gcda avec Xcode5, le simulateur iOS7 et XCTest

Inspirée par la solution de cette question, j'ai essayé d'utiliser la même approche avec XCTest.

J'ai mis "Générer de la Couverture de Test des Fichiers=OUI" et "Instrument de Flux de Programme=OUI".

XCode encore ne produit pas de gcda fichiers. Quelqu'un a une idée comment résoudre ce problème?

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

Dans AppDelegate.m j'ai:

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

EDIT: j'ai édité la question afin de refléter l'état actuel (sans les harengs rouges).

MODIFIER Pour le faire fonctionner, j'ai dû ajouter tous les fichiers en cours de test pour le test de la cible, y compris 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();
    }
}

44voto

Hugo Ferreira Points 538

Mise à jour 1:

Après avoir lu un peu plus sur le sujet, 2 choses ont maintenant devenu clair pour moi (nous soulignons):

Les Tests et le test de l'application sont compilés séparément. Les Tests sont en réalité injecté dans l'exécution de l'application, de sorte que l' __gcov_flush() doit être appelée à l'intérieur de l'application et non pas dans les tests.

- Xcode5 de Couverture de Code (à partir de cmd ligne pour CI s'appuie) - Débordement de Pile

et,

Nouveau: l'Injection est complexe. Votre emporter doit être: Ne pas ajouter .m des fichiers à partir de votre application à votre cible de test. Vous obtiendrez un comportement inattendu.

- Test De La Vue Des Contrôleurs – #1 – Briquet Vue Contrôleurs

Le code ci-dessous a été modifiée pour tenir compte de ces deux idées...


Mise à jour 2:

Ajout d'informations sur la façon de faire ce travail pour les bibliothèques statiques, comme demandé par @MdaG dans les commentaires. Les principaux changements pour les bibliothèques, c'est que:

  • On peut rincer directement à partir de l' -stopObserving méthode car il n'y a pas une application à part, où injecter les tests.

  • Nous devons inscrire l'observateur dans l' +load méthode parce que le temps de l' +initialize est appelé (lorsque la classe est d'abord accessible à partir de la suite de tests), il est déjà trop tard pour XCTest pour le ramasser.


Solution

Les autres réponses ici m'ont aidé énormément dans la mise en place de la couverture de code dans mon projet. Tout en explorant, je crois que j'ai réussi à simplifier le code pour le corriger un peu.

Soit l'un des:

  • ExampleApp.xcodeproj créé à partir de zéro comme un "Vide de l'Application"
  • ExampleLibrary.xcodeproj créé en tant qu'indépendant "Cocoa Touch Bibliothèque Statique"

Ces mesures que j'ai pris pour permettre la Couverture de Code génération dans Xcode 5:

  1. Créer l' GcovTestObserver.m le fichier avec le code suivant, à l'intérieur de la ExampleAppTests groupe:

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

    Lors d'une bibliothèque, puisqu'il n'est pas d'application pour appeler, la chasse d'eau peut être invoqué directement à partir de l'observateur. Dans ce cas, ajouter le fichier à la ExampleLibraryTests groupe avec ce code à la place:

    #import <XCTest/XCTestObserver.h>
    
    @interface GcovTestObserver : XCTestObserver
    @end
    
    @implementation GcovTestObserver
    
    - (void)stopObserving
    {
        [super stopObserving];
        extern void __gcov_flush(void);
        __gcov_flush();
    }
    
    @end
    
  2. Pour vous inscrire au test d'observateur catégorie, ajoutez le code suivant à l' @implementation de la section de l'un de:

    • ExampleAppDelegate.m le fichier, à l'intérieur de la ExampleApp groupe
    • ExampleLibrary.m le fichier, à l'intérieur de la ExampleLibrary groupe

     

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

    Auparavant, cette réponse m'a suggéré d'utiliser l' +initialize méthode (et vous pouvez encore le faire dans le cas d'Applications) mais il ne fonctionne pas pour les bibliothèques...

    Dans le cas d'une bibliothèque, l' +initialize sera probablement exécuté uniquement lorsque les tests invoquer le code de la bibliothèque pour la première fois, et il est alors déjà trop tard pour s'inscrire à l'observateur. À l'aide de l' +load méthode, l'observateur de l'enregistrement des toujours fait dans le temps, quel que soit le scénario.

  3. Dans le cas d'Applications, ajoutez le code suivant à l' @implementation de la section de l' ExampleAppDelegate.m le fichier, à l'intérieur de la ExampleApp groupe, pour vider les fichiers de couverture lors de la sortie de l'application:

    - (void)applicationWillTerminate:(UIApplication *)application
    {
    #ifdef DEBUG
        extern void __gcov_flush(void);
        __gcov_flush();
    #endif
    }
    
  4. Activer Generate Test Coverage Files et Instrument Program Flow par régler l' YES dans le projet, les paramètres de construction (pour la "Exemple" et "Exemple des Tests de" cibles).

    Pour ce faire, dans un simple et cohérente, j'ai ajouté un Debug.xcconfig le fichier associée avec le projet de "Debug" de configuration, avec les déclarations suivantes:

    GCC_GENERATE_TEST_COVERAGE_FILES = YES
    GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES
    
  5. Assurez-vous que tous les projets de l' .m des fichiers sont également inclus dans la Compilation des Sources" phase de construction de la "Exemple des Tests" de la cible. Ne pas le faire: le code de l'application appartient à l'application cible, le code de test appartient à la cible de test!

Après l'exécution de tests pour votre projet, vous l être en mesure de trouver le générés fichiers de couverture pour l' Example.xcodeproj ici:

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

Notes

Étape 1

La déclaration de méthode à l'intérieur d' XCTestObserver.h indique:

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

Étape 2

2.a)

Par la création et l'enregistrement d'un distinct XCTestObserver sous-classe, nous éviter d'avoir à interférer directement avec la valeur par défaut XCTestLog classe.

La clé constante déclaration à l'intérieur d' XCTestObserver.h suggère simplement que:

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

Même si il est courant d'utiliser if(self == [ExampleAppDelegate class]) autour du code à l'intérieur d' +initialize [Note: c'est maintenant à l'aide d' +load], je trouve plus facile de les omettre dans ce cas particulier: aucun besoin d'ajuster le bon nom de la classe lorsque vous faites des copier-coller.

Aussi, la protection contre l'exécution du code à deux reprises n'est pas vraiment nécessaire ici: ce n'est pas inclus dans les versions release, et même si nous sous - ExampleAppDelegate il n'y a pas de problème dans l'exécution de ce code plus d'un.

2.c)

Dans le cas des bibliothèques, le premier indice sur le problème venait de ce commentaire de code dans la boîte à outils Google pour Mac projet: 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.
  // (...)

Et comme la Classe NSObject de Référence indique:

initialiser - Initialise la classe avant qu'il reçoit son premier message

charge - Invoqué chaque fois qu'une classe ou d'une catégorie est ajoutée à l'Objective-C runtime

Le "EmptyLibrary" projet

Dans le cas où quelqu'un essaie de reproduire ce processus en créant leur propre "EmptyLibrary" projet, s'il vous plaît garder à l'esprit que vous avez besoin d'invoquer le code de la bibliothèque de la valeur par défaut vide tests en quelque sorte.

Si la bibliothèque principale de la classe n'est pas invoquée dans tous les tests, le compilateur va essayer d'être intelligent et de ne pas ajouter à la runtime (puisque c'est de ne pas être appelé n'importe où), donc le +load méthode n'est pas appelée.

Vous pouvez simplement appeler inoffensive méthode (comme Apple suggère dans leurs Directives de Codage pour le Cacao # Initialisation de Classe). Par exemple:

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

3voto

sanderson Points 31

Parce que vous devez créer un nouveau XCTestSuiteRun exemple dans le testSuiteDidStop méthode, vous n'allez pas obtenir un bon résultat sur une == check. Au lieu de dépendre de l'instance de l'égalité, nous avons utilisé un simple compteur et appeler flush quand il atteint zéro, il va quand le haut-niveau XCTestSuite termine son exécution. Il y a probablement plus intelligent de façons de le faire.

Tout d'abord, nous avons dû mettre de "Générer de la Couverture de Test des Fichiers=OUI" et "Instrument de Flux de Programme=OUI" à la fois de l'Essai et de l'application principale cibles.

#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

Il y a une étape supplémentaire est nécessaire, parce que l' +initialiser appel n'a pas été faite sur l'observateur lorsqu'il est inclus dans la cible de Test.

Dans l'AppDelegate, ajoutez la ligne suivante:

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

1voto

Jasper Blues Points 7262

Voici une autre solution qui évite d'avoir à modifier votre AppDelegate

UIApplication + Instrumented.m (mettez ceci dans votre cible principale):

 @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 (mettez ceci dans votre cible de test):

 @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
 

0voto

Sulthan Points 23360

- (void)applicationWillTerminate:(UIApplication*)application doit être défini dans votre délégué à l'application, pas dans la classe observateur.

Je n'ai eu aucun problème de bibliothèque. "-lgov" n'est pas nécessaire et vous n'avez pas besoin d'ajouter de bibliothèque. La couverture est directement prise en charge par le compilateur LLVM.

0voto

itsthejb Points 345

Le processus pour cela est un peu différent si vous utilisez Specta, car il fait ses propres erreurs. Ce qui suit fonctionne pour moi:

Test Bundle:

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

@implementation MyReporter

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

@end
 

AppDéléguer:

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

Vous devez ensuite activer votre sous-classe de rapporteur personnalisée en définissant la variable d'environnement SPECTA_REPORTER_CLASS à MyReporter dans la section Exécuter de votre schéma principal.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X