2015-09-17 15 views
14

Ho scoperto che Xcode 7 (Versione 7.0 (7A220)) ha cambiato l'ordine in cui i metodi +load per le classi e le categorie vengono chiamati durante i test unitari.Ordine di metodo + carico modificato in Xcode 7

Se una categoria appartenente al target di test implementa un metodo +load, viene ora chiamato alla fine, quando potrebbero essere già state create e utilizzate istanze della classe.

Ho un AppDelegate, che implementa il metodo +load. Il file AppDelegate.m contiene anche la categoria AppDelegate (MainModule). Inoltre, esiste un file di test unitario LoadMethodTestTests.m, che contiene un'altra categoria - AppDelegate (UnitTest).

Entrambe le categorie implementano anche il metodo +load. La prima categoria appartiene all'obiettivo principale, il secondo - all'obiettivo del test.

Codice

ho fatto un piccolo test project per dimostrare il problema. Si tratta di un progetto di visualizzazione Xcode predefinito vuoto con solo due file modificati.

AppDelegate.m:

#import "AppDelegate.h" 

@implementation AppDelegate 

+(void)load { 
    NSLog(@"Class load"); 
} 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 
    NSLog(@"didFinishLaunchingWithOptions"); 

    return YES; 
} 

@end 

@interface AppDelegate (MainModule) 
@end 

@implementation AppDelegate (MainModule) 

+(void)load { 
    NSLog(@"Main Module +load"); 
} 

@end 

E un file di test di unità (LoadMethodTestTests.m):

#import <UIKit/UIKit.h> 
#import <XCTest/XCTest.h> 
#import "AppDelegate.h" 

@interface LoadMethodTestTests : XCTestCase 

@end 

@interface AppDelegate (UnitTest) 
@end 

@implementation AppDelegate (UnitTest) 

+(void)load { 
    NSLog(@"Unit Test +load"); 
} 

@end 

@implementation LoadMethodTestTests 

-(void)testEmptyTest { 
    XCTAssert(YES); 
} 

@end 

Testing

ho eseguito Unit Testing di questo progetto (il codice e la github link is below) su Xcode 6/7 e ha ottenuto il seguente ordine +load:

Xcode 6 (iOS 8.4 simulator): 
    Unit Test +load 
    Class load 
    Main Module +load 
    didFinishLaunchingWithOptions 

Xcode 7 (iOS 9 simulator): 
    Class load 
    Main Module +load 
    didFinishLaunchingWithOptions 
    Unit Test +load 

Xcode 7 (iOS 8.4 simulator): 
    Class load 
    Main Module +load 
    didFinishLaunchingWithOptions 
    Unit Test +load 

Domanda

Xcode 7 corre il metodo categoria +load bersaglio di prova (Unit Test +load), alla fine, dopo che la AppDelegate è già stato creato. È un comportamento corretto o è un bug che dovrebbe essere inviato ad Apple?

Può essere che non sia specificato, quindi il compilatore/runtime è libero di riorganizzare le chiamate? Ho dato un'occhiata allo this SO question e allo +load description in the NSObject documentation ma non ho capito bene come si suppone che il metodo +load funzioni quando la categoria appartiene a un altro target.

O può essere AppDelegate è una sorta di caso speciale per qualche motivo?

questo che sto chiedendo questo

  1. scopi didattici.
  2. Ho usato per eseguire il metodo di swizzling in una categoria all'interno del target di test unitario. Ora, quando l'ordine di chiamata è cambiato, applicationDidFinishLaunchingWithOptions viene eseguito prima che abbia luogo lo swizzling. Ci sono altri modi per farlo, credo, ma mi sembra semplicemente controintuitivo il modo in cui funziona in Xcode 7.Ho pensato che quando una classe viene caricata in memoria, è necessario chiamare +load di questa classe e i metodi +load di tutte le sue categorie prima di poter qualcosa con questa classe (come creare un'istanza e chiamare didFinishLaunching...).

risposta

19

TL, DR: È colpa di xctest, non di objc.

Questo perché di come il xctest eseguibile (quello che esegue effettivamente i test di unità, situato a $XCODE_DIR/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest caricherà il suo fascio.

Pre-Xcode 7, è caricato tutti fasci prova riferimento prima di eseguire qualsiasi test Questo può essere visto (per coloro che si preoccupano), smontando il file binario per Xcode 6.4, la relativa sezione può essere vista per il simbolo -[XCTestTool runTestFromBundle:].

Nella versione Xcode 7 di xctest, è possibile vedere che si ritarda il caricamento di testare i pacchetti finché il test effettivo non viene eseguito da XCTestSuite, nell'attuale quadro XCTest, che può essere visualizzato nel simbolo __XCTestMain, che viene richiamato solo DOPO che l'applicazione host del test è impostata.

Poiché l'ordine di questi invocati viene modificato internamente, il modo in cui vengono invocati i metodi del test +load è diverso. Non sono state apportate modifiche agli interni dell'obiettivo-c-runtime.

Se si desidera risolvere questo problema nell'applicazione, è possibile eseguire alcune operazioni. Innanzitutto, è possibile caricare manualmente il pacchetto utilizzando +[NSBundle bundleWithPath:] e richiamando -load.

È inoltre possibile collegare il target di test all'applicazione host di test (spero che si stia utilizzando un host di test separato rispetto all'applicazione principale!), Il che farebbe sì che venga caricato automaticamente quando xctest carica l'applicazione host.

Non lo considererei un bug, è solo un dettaglio di implementazione di XCTest.

Fonte: basta passare gli ultimi 3 giorni a smontare lo xctest per un motivo completamente non correlato.

+0

Grazie mille! Per essere onesti, non mi aspettavo di ottenere una risposta così rapida e accurata per questa domanda. Sono fortunato :) Inoltre, grazie per i suggerimenti proposti. – FreeNickname

+0

Interessante. Questo spiegherebbe che i metodi '+ load' vengano chiamati due volte come sto vedendo qui? http://stackoverflow.com/questions/33154729/xcode-7-xctestkiwi-load-category-methods-called-twice –

+0

Ho un'altra domanda per te se non ti dispiace. Hai detto che è una buona pratica usare un host separato per i test unitari. Ho provato a utilizzare materiali Google per le best practice sui test dell'unità iOS, ma sfortunatamente non ho trovato nulla sull'argomento. Avete collegamenti a qualsiasi materiale in cui potrei leggere i modi migliori per implementarlo? Ho provato a duplicare semplicemente il target principale, ma sembra che in questo modo dovrò mantenere manualmente i target sincronizzati. Pensavo che dovesse esserci un modo migliore. – FreeNickname

4

Xcode 7 ha due diversi ordini di caricamento nel progetto modello iOS.

Caso di test unità. Per Test unità, il gruppo di test è immesso nella la simulazione in esecuzione dopo che l'applicazione è stata avviata alla schermata principale. L'Unità di default sequenza di esecuzione di test è simile al seguente: Test Case

Application: AppDelegate initialize() 
Application: AppDelegate init() 
Application: AppDelegate application(…didFinishLaunchingWithOptions…) 
Application: ViewController viewDidLoad() 
Application: ViewController viewWillAppear() 
Application: AppDelegate applicationDidBecomeActive(…) 
Application: ViewController viewDidAppear() 
Unit Test: setup() 
Unit Test: testExample() 

UI. Per il test dell'interfaccia utente, è impostato un secondo processo XCTRunner che esegue l'applicazione in prova. Un argomento può essere passato dal test setUp() ...

class Launch_UITests: XCTestCase { 

    override func setUp() { 
    // … other code … 
    let app = XCUIApplication() 
    app.launchArguments = ["UI_TESTING_MODE"] 
    app.launch() 
    // … other code … 
    } 

... da ricevere il AppDelegate ...

class AppDelegate: UIResponder, UIApplicationDelegate { 

    func application(… didFinishLaunchingWithOptions…) -> Bool { 
    // … other code … 
    let args = NSProcessInfo.processInfo().arguments 
    if args.contains("UI_TESTING_MODE") { 
     print("FOUND: UI_TESTING_MODE") 
    } 
    // … other code … 

Il iniezione nel vs processo separato osservabile stampando NSProcessInfo.processInfo().processIdentifier e NSProcessInfo.processInfo().processName dalla sia il codice di prova e il codice dell'applicazione.