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:
-
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
-
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.
-
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
}
-
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
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];
}