web-dev-qa-db-fra.com

Comment faire savoir à l'application si ses tests unitaires en cours d'exécution s'exécutent dans un pur projet Swift?

Une chose gênante lors de l'exécution de tests dans XCode 6.1 est que toute l'application doit exécuter et lancer son storyboard et son viewController racine. Dans mon application, cela exécute des appels de serveur qui récupèrent les données de l'API. Cependant, je ne veux pas que l'application fasse cela lors de l'exécution de ses tests.

Avec les macros de préprocesseur disparues, quel est le mieux pour mon projet de savoir qu'il a été lancé avec des tests et non un lancement ordinaire? Je les exécute normalement avec CMD + U et sur un bot. 

Le pseudo-code serait:

// Appdelegate.Swift

if runningTests() {
   return
} else {
   // do ordinary api calls
}
60
bogen

Au lieu de vérifier si les tests sont en cours pour éviter les effets secondaires, vous pouvez les exécuter sans l'application hôte elle-même. Allez dans Paramètres du projet -> sélectionnez la cible du test -> Général -> Tests -> Application hôte -> sélectionnez "Aucun" . N'oubliez pas d'inclure tous les fichiers nécessaires à l'exécution des tests, ainsi que les bibliothèques normalement incluses par la cible de l'application hôte.

enter image description here

34

La réponse d'Elvind n'est pas mauvaise si vous voulez avoir ce qu'on appelait autrefois des "tests logiques" purs. Si vous souhaitez toujours exécuter votre application hôte contenant encore exécuter de manière conditionnelle ou ne pas exécuter de code selon que les tests sont exécutés, vous pouvez utiliser les éléments suivants pour détecter si un ensemble de tests a été injecté:

if NSProcessInfo.processInfo().environment["XCTestConfigurationFilePath"] != nil {
     // Code only executes when tests are running
}

J'ai utilisé un indicateur de compilation conditionnel comme décrit dans cette réponse pour que le coût d'exécution ne soit encouru que dans les versions de débogage:

#if DEBUG
    if NSProcessInfo.processInfo().environment["XCTestConfigurationFilePath"] != nil {
        // Code only executes when tests are running
    }
#endif

Modifier Swift 3.0

if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil {
    // Code only executes when tests are running
}
62
Michael McGuire

J'utilise ceci dans l'application: didFinishLaunchingWithOptions:

// Return if this is a unit test
if let _ = NSClassFromString("XCTest") {
    return true
}
30
Jesse

Je pense qu'il est tout à fait légitime de vouloir savoir si vous exécutez un test ou non. Cela peut être utile pour de nombreuses raisons. Par exemple, lors de l'exécution de tests, je reviens tôt des méthodes d'application-did/will-finish-lance dans App Delegate, accélérant ainsi les tests pour le code non conforme à mon test unitaire. Pourtant, je ne peux pas faire de test de "logique" pur, pour une foule d'autres raisons.

J'avais l'habitude d'utiliser l'excellente technique décrite par @Michael McGuire ci-dessus. Cependant, j'ai remarqué que j'avais cessé de travailler pour moi autour de Xcode 6.4/iOS8.4.1 (peut-être que ça s'est cassé plus tôt).

Notamment, je ne vois plus le XCInjectBundle lors de l'exécution d'un test dans une cible de test pour une structure de la mienne. C'est-à-dire que je cours à l'intérieur d'une cible de test qui teste un framework.

Donc, en utilisant l'approche suggérée par @Fogmeister, chacun de mes schémas de test définit maintenant une variable d'environnement que je peux vérifier.

 enter image description here

Ensuite, voici le code que j'ai sur une classe appelée APPSTargetConfiguration qui peut répondre à cette question simple pour moi.

static NSNumber *__isRunningTests;

+ (BOOL)isRunningTests;
{
    if (!__isRunningTests) {
        NSDictionary *environment = [[NSProcessInfo processInfo] environment];
        NSString *isRunningTestsValue = environment[@"APPS_IS_RUNNING_TEST"];
        __isRunningTests = @([isRunningTestsValue isEqualToString:@"YES"]);
    }

    return [__isRunningTests boolValue];
}

Le seul inconvénient de cette approche est que, si vous exécutez un test depuis votre schéma d'application principal, comme XCTest vous le permet (c'est-à-dire, en ne sélectionnant pas l'un de vos schémas de test), vous n'obtiendrez pas cet ensemble de variable d'environnement.

16
idStar

Autre, à mon avis plus simple:

Vous éditez votre schéma pour transmettre une valeur booléenne en tant qu'argument de lancement à votre application. Comme ça:

 Set launch arguments in Xcode

Tous les arguments de lancement sont automatiquement ajoutés à votre NSUserDefaults.

Vous pouvez maintenant obtenir le BOOL comme:

BOOL test = [[NSUserDefaults standardUserDefaults] boolForKey:@"isTest"];
13
iCaramba
var isRunningTests: Bool {
    return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
}

Usage

if isRunningTests {
    return "lena.bmp"
}
return "facebook_profile_photo.bmp"
3
neoneye

Vous pouvez passer des arguments d'exécution dans l'application en fonction du schéma ici ...

enter image description here

Mais je me demande si c'est vraiment nécessaire ou non.

2
Fogmeister

Voici une méthode que j'utilise dans Swift 4/Xcode 9 pour nos tests unitaires. C'est basé sur la réponse de Jesse .

Il n'est pas facile d'empêcher le storyboard d'être chargé du tout, mais si vous ajoutez ceci au début de didFinishedLaunching, cela indique clairement à vos développeurs ce qui se passe:

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions:
                 [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    #if DEBUG
    if let _ = NSClassFromString("XCTest") {
        // If we're running tests, don't launch the main storyboard as
        // it's confusing if that is running fetching content whilst the
        // tests are also doing so.
        let viewController = UIViewController()
        let label = UILabel()
        label.text = "Running tests..."
        label.frame = viewController.view.frame
        label.textAlignment = .center
        label.textColor = .white
        viewController.view.addSubview(label)
        self.window!.rootViewController = viewController
        return true
    }
    #endif

(Évidemment, vous ne devriez rien faire de pareil pour les tests d’interface utilisateur où vous voulez que l’application démarre normalement!)

2
JosephH

Approche combinée de @ Jessy et de Michael McGuire

(Une réponse acceptée ne vous aidera pas lors de l'élaboration d'un cadre)

Alors voici le code:

#if DEBUG
        if (NSClassFromString(@"XCTest") == nil) {
            // Your code that shouldn't run under tests
        }
#else
        // unconditional Release version
#endif
1
m8labs

Certaines de ces approches ne fonctionnent pas avec les UITests et si vous testez essentiellement avec le code de l'application elle-même (plutôt que d'ajouter du code spécifique dans une cible UITest).

J'ai fini par définir une variable d'environnement dans la méthode setUp du test:

XCUIApplication *testApp = [[XCUIApplication alloc] init];

// set launch environment variables
NSDictionary *customEnv = [[NSMutableDictionary alloc] init];
[customEnv setValue:@"YES" forKey:@"APPS_IS_RUNNING_TEST"];
testApp.launchEnvironment = customEnv;
[testApp launch];

Notez que ceci est sans danger pour mes tests car je n’utilise actuellement aucune autre valeur launchEnvironment; Si vous le faites, vous voudrez bien sûr commencer par copier les valeurs existantes.

Ensuite, dans mon code d'application, je recherche cette variable d'environnement si/lorsque je souhaite exclure certaines fonctionnalités lors d'un test:

BOOL testing = false;
...
if (! testing) {
    NSDictionary *environment = [[NSProcessInfo processInfo] environment];
    NSString *isRunningTestsValue = environment[@"APPS_IS_RUNNING_TEST"];
    testing = [isRunningTestsValue isEqualToString:@"YES"];
}

Remarque - merci pour le commentaire de RishiG qui m'a donné cette idée; Je viens d'élargir cela à un exemple.

0
ODB