web-dev-qa-db-fra.com

Les tests unitaires sous Xcode exécutent-ils l'application?

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?

44
Mark W

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;
}
61
Sulthan

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
20
dwb

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.

9
Tomasz Bąk

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))
}
5
Rémy Virin

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

2
nepo

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. 

1
sean woodward

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.

 enter image description here

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
1
rustylepord

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.

1
Benjohn

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
    ....
}
0
estemendoza

Vous pouvez le faire en définissant l'application hôte sur Aucune dans votre cible de tests.

 enter image description here

0
Francois Nadeau