2013-03-30 8 views
43

Ich stoße auf ein seltsames Problem, auf das ich noch nie gestoßen bin.Unit Testing in Xcode, wird die App ausgeführt?

Wenn Sie cmd + U ausführen, um Ihre Unit Tests (OCUnit zum Beispiel) zu starten ruft es tatsächlich die main.m, neu die appDelegate und führen Sie die App, als ob Sie cmd + R gedrückt hätten?

Ich frage nur, weil ich CoreData hinter diesem DataLayer verwende. Ich verspotte den DataLayer erfolgreich in meinen Tests, aber sobald ich eine getAll-Methode implementiert habe, die CoreData tatsächlich aufruft, wirft die app/xcode eine Ausnahme auf das verwaltete Objektmodell kann nicht Null sein. Was ich verstehe, aber ich habe nicht vor, die DataLayer-Klasse neu zu erstellen, und ich habe einen Breakpoint in meine MainViewController loadView-Methode gesetzt, wo sie die DataLayer getAll-Methode aufruft. Es sollte bei Tests keine Rolle spielen, da es sich um ein Scheinobjekt handelt, das aber scheinbar die reale Instanz aufruft.

Also zurück zu meiner Frage, beim Drücken von cmd + U führt es auch zuerst die App und dann die Tests?

Antwort

60

Die Anwendung wird tatsächlich ausgeführt, aber es gibt einen Trick, den Sie verwenden können, um zu verhindern, dass sie ausgeführt wird.

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; 
} 
+1

Sehr schön, ich habe nie daran gedacht, einen anderen App-Delegierten zu ersetzen! Ich würde den Test vereinfachen, um nur '' BOOL runningTests = NSClassFromString (@ "SenTestCase")! = Nil; '' –

+0

@JonReid Schöne Ergänzung. Ich habe nie daran gedacht, es auf diese Weise zu vereinfachen! – Sulthan

+2

Für XCODE 5 - BOOL inTests = (NSClassFromString (@ "XCTest")! = Nil); – Pion

1

Ja, Ihr Testziel hat eine Zielabhängigkeit zum App-Ziel. Daher wird das App-Ziel erstellt, wenn Sie Cmd + U oder Cmd + Shift + U drücken.

20

Hier ist eine Variation der Antwort des Sulthan der XCTest verwendet, die von XCode der Standard für Testklassen erzeugen 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"); 
     } 
    } 
} 

Die in main.m geht, die unter Unterstützung Dateien sein sollte in einem Standardprojektlayout.

dann in Ihren Tests Verzeichnis add:

TestAppDelegate.h


#import <Foundation/Foundation.h> 

@interface TestAppDelegate : NSObject<UIApplicationDelegate> 
@end 

TestAppDelegate.m


#import "TestAppDelegate.h" 

@implementation TestAppDelegate 
@end 
5

Wenn Sie Swift verwenden (Sie wahrscheinlich don‘ t haben eine main.c), müssen Sie diese Schritte tun:

1: Entfernen @UIApplicationMain in AppDelegate.swift

2: Erstellen einer leeren TestingAppDelegate.swift

import UIKit 
class TestingAppDelegate: UIResponder, UIApplicationDelegate { 
    var window: UIWindow? 
} 

3: Erstellen einer Datei main.swift genannt:

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)) 
} 
+3

XCode 7 (Beta 5) + iOS9 - Verwenden Sie Process.argc, Process.unsafeArgv anstelle von C_ARGC, C_ARGV –

+1

Mit diesem Ansatz führt das Testen von AppDelegate dazu, dass das Storyboard noch geladen wird?Und wenn ja, kann dies vermieden werden? –

+0

Full Decent - Ja, es lädt noch. Es gibt einen Weg, aber ich würde gerne einen besseren finden. Duplizieren Sie das Ziel, das getestet wird, und nennen Sie es myApp und die Duplizierungs-myApp-Kopie. Deaktivieren Sie die Mitgliedschaft für Main.storyboard für das myApp-Kopierziel. Fügen Sie ein neues Storyboard namens "Main.storyboard" hinzu und machen Sie es zum Mitglied des myApp-Kopierziels. Ändern Sie das myApp-Testziel, sodass es anstelle von myApp myApp-Kopie testet. Jetzt wird das leere Main.storyboard verwendet und keine View-Controller werden instanziiert. Ich möchte jedoch einen einfacheren Weg finden. –

8

In Swift, I prefere normal zu umgehen Ausführungspfad innerhalb 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 
} 

Der Code innerhalb guard entfernt alle Ansichten, die vom Storyboard erstellt wurden.

0

Ich verwende den Ansatz von Tomasz Bak sowie einige Code von dwb Antwort und kommen mit den folgenden:

- (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 
    .... 
} 
1

fand ich eine andere Lösung für das Problem:

int main(int argc, char * argv[]) 
{ 
    @autoreleasepool { 
     return UIApplicationMain(argc, argv, nil, ({ 
      ![NSProcessInfo processInfo].environment[@"XCTestConfigurationFilePath"] ? 
      @"AppDelegate" : 
      nil;     
     })); 
    } 
} 

Von hier aus: http://qualitycoding.org/app-delegate-for-tests/#comment-63984

0

Mit xCode 7 und xCtool

xctool kann Komponententests ausführen, ohne die App auszuführen.

diese Funktion zu erhalten,

1. Aktualisieren Sie die Zieleinstellungen so, dass sie ohne eine Host-App ausgeführt werden.

Wählen Sie Ihr Projekt -> dann Ziel testen -> Setzen Sie die Host-Anwendung auf keine.

enter image description here

2. Installieren Sie xctool, wenn Sie es nicht haben.

brew install xctool 

3. Führen Sie die Tests mit xctool mit Terminal.

xctool -workspace yourWorkspace.xcworkspace -scheme yourScheme run-tests -sdk iphonesimulator 
0

Excellent answers oben, der die Anwendung Delegierten zur Laufzeit dynamisch ändernden empfehlen.

Die kleine Änderung, die ich mache, ist detect a unit test run durch Abfrage NSProcessInfo. Der Vorteil ist, dass Sie keine Klasse benötigen, die erkannt werden kann, um zu sehen, ob Komponententests ausgeführt werden.

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); 
     } 
    } 

Die @"XCInjectBundleInto" Eigenschaft in environmentDictionary ist der Pfad zu Ihrem Bündel Unit-Tests und wird von Xcode eingerichtet.