Je rencontre un problème étrange que je n’ai jamais rencontré auparavant.
Lorsque vous exécutez cmd + U pour exécuter vos tests unitaires (OCUnit par exemple), le système appelle-t-il réellement le fichier main.m, nouveau, le appDelegate et exécutez-vous comme si vous aviez appuyé sur cmd + R?
Je demande seulement parce que j'utilise CoreData derrière ce DataLayer. Je moque avec succès le DataLayer dans mes tests, mais une fois que j'ai implémenté une méthode getAll qui appelle réellement CoreData, l'application/xcode lève une exception concernant le modèle d'objet géré ne peut pas être nulle. Ce que je comprends, mais je ne veux pas réellement créer une nouvelle classe DataLayer, et j’ai mis un point d’arrêt dans ma méthode mainviewcontroller loadView où elle appelle la méthode DataLayer getAll. Les tests ne devraient pas avoir d’importance, car il s’agit d’un objet fictif, mais il appelle apparemment l’instance réelle.
Revenons donc à ma question. Lorsque vous appuyez sur cmd + U, l'application est-elle d'abord exécutée, puis les tests?
L'application est en cours d'exécution, mais il existe une astuce que vous pouvez utiliser pour l'empêcher de s'exécuter.
int main(int argc, char* argv[]) {
int returnValue;
@autoreleasepool {
BOOL inTests = (NSClassFromString(@"SenTestCase") != nil
|| NSClassFromString(@"XCTest") != nil);
if (inTests) {
//use a special empty delegate when we are inside the tests
returnValue = UIApplicationMain(argc, argv, nil, @"TestsAppDelegate");
}
else {
//use the normal delegate
returnValue = UIApplicationMain(argc, argv, nil, @"AppDelegate");
}
}
return returnValue;
}
Voici une variante de la réponse de Sulthan qui utilise XCTest, qui est la valeur par défaut pour les classes de test générées par XCode 5.
int main(int argc, char * argv[])
{
@autoreleasepool {
BOOL runningTests = NSClassFromString(@"XCTestCase") != nil;
if(!runningTests)
{
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
else
{
return UIApplicationMain(argc, argv, nil, @"TestAppDelegate");
}
}
}
Cela va dans main.m, qui devrait être sous Fichiers de support dans une mise en page de projet standard.
Ensuite, dans votre répertoire de tests, ajoutez:
TestAppDelegate.h
#import <Foundation/Foundation.h>
@interface TestAppDelegate : NSObject<UIApplicationDelegate>
@end
TestAppDelegate.m
#import "TestAppDelegate.h"
@implementation TestAppDelegate
@end
Dans Swift, je préfère ignorer un chemin d'exécution normal dans application: didFinishLaunchingWithOptions
:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
guard normalExecutionPath() else {
window = nil
return false
}
// regular setup
return true
}
private func normalExecutionPath() -> Bool {
return NSClassFromString("XCTestCase") == nil
}
Le code dans guard
supprimera toutes les vues créées à partir du storyboard.
Si vous utilisez Swift (vous n'avez probablement pas de main.c
), procédez comme suit:
1: supprimer @UIApplicationMain
dans AppDelegate.Swift
2: Créer un TestingAppDelegate.Swift
vide
import UIKit
class TestingAppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
}
3: Créez un fichier appelé main.Swift
:
import Foundation
import UIKit
let isRunningTests = NSClassFromString("XCTestCase") != nil
if isRunningTests {
UIApplicationMain(C_ARGC, C_ARGV, nil, NSStringFromClass(TestingAppDelegate))
} else {
UIApplicationMain(C_ARGC, C_ARGV, nil, NSStringFromClass(AppDelegate))
}
J'ai trouvé une autre solution au problème:
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, ({
![NSProcessInfo processInfo].environment[@"XCTestConfigurationFilePath"] ?
@"AppDelegate" :
nil;
}));
}
}
De là: http://qualitycoding.org/app-delegate-for-tests/#comment-63984
Oui, votre cible de test aura une dépendance de cible par rapport à la cible de l'application. Par conséquent, la cible de l'application sera construite lorsque vous appuierez sur Cmd + U ou Cmd + Maj + U.
Utilisation de xCode 7 et xCtool
xctool est capable d'exécuter des tests unitaires sans exécuter l'application.
Pour que cela fonctionne,
1. Mettez à jour les paramètres de cible pour qu'ils s'exécutent sans application hôte.
Sélectionnez votre projet -> puis testez la cible -> Définissez l'application hôte sur none.
2. Installez xctool, si vous ne l'avez pas.
brew install xctool
3. Exécutez les tests avec terminal avec xctool.
xctool -workspace yourWorkspace.xcworkspace -scheme yourScheme run-tests -sdk iphonesimulator
Excellent réponses ci-dessus suggèrent de changer dynamiquement le délégué de l'application au moment de l'exécution.
La petite modification que je fais est de détecter un test unitaire en interrogeant NSProcessInfo
. L'avantage est qu'il n'est pas nécessaire d'avoir une classe pouvant être détectée pour voir si les tests unitaires sont en cours d'exécution.
int main(int argc, char * argv[])
{
// Put your App delegate class here.
const Class appDelegateClass = [ATAppDelegate class];
NSDictionary *const environmentDictionary =
[[NSProcessInfo processInfo] environment];
const BOOL runningUnitTests =
environmentDictionary[@"XCInjectBundleInto"] != nil;
NSString *delegateName =
runningUnitTests ? nil : NSStringFromClass(appDelegateClass);
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, delegateName);
}
}
La propriété @"XCInjectBundleInto"
dans environmentDictionary
est le chemin d'accès à votre groupe de tests unitaires et est configurée par Xcode.
J'utilise l'approche de Tomasz Bak avec un code de réponse dwb et propose les éléments suivants:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
BOOL runningTests = NSClassFromString(@"XCTestCase") != nil;
if (runningTests) {
self.window.rootViewController = [UIViewController new];
return true;
}
// Your normal code below this
....
}