large bar ios objective-c unit-testing xcode7 objective-c-runtime

ios - bar - Se cambió el orden del método de carga en Xcode 7



large title navigation bar swift (2)

Descubrí que Xcode 7 (Versión 7.0 (7A220)) cambió el orden en el que se llaman los métodos de +load para clases y categorías durante las pruebas unitarias.

Si una categoría que pertenece al objetivo de la prueba implementa un método de +load , ahora se llama al final, cuando es posible que ya se hayan creado y utilizado instancias de la clase.

Tengo un AppDelegate , que implementa +load método de +load . El archivo AppDelegate.m también contiene la categoría AppDelegate (MainModule) . Además, hay un archivo de prueba de unidad LoadMethodTestTests.m , que contiene otra categoría: AppDelegate (UnitTest) .

Ambas categorías también implementan +load método +load . La primera categoría pertenece al objetivo principal, la segunda, al objetivo de prueba.

Código

Hice un pequeño proyecto de prueba para demostrar el problema. Es un proyecto de una vista de Xcode one vacío vacío con solo dos archivos cambiados.

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

Y un archivo de prueba de unidad (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

Pruebas

Realicé la prueba unitaria de este proyecto (el código y el enlace de github están debajo) en Xcode 6/7 y obtuve el siguiente orden de +load llamadas de +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

Pregunta

Xcode 7 ejecuta la categoría objetivo de prueba +load método de +load ( Unit Test +load ) al final, después de que ya se haya creado el AppDelegate . ¿Es un comportamiento correcto o es un error que debe enviarse a Apple?

¿Puede que no esté especificado, por lo que el compilador / tiempo de ejecución tiene la libertad de reorganizar las llamadas? Eché un vistazo a esta pregunta de SO así como a la descripción de + carga en la documentación de NSObject pero no entendí bien cómo se supone que funciona el método de +load cuando la categoría pertenece a otro objetivo.

¿O puede ser que AppDelegate sea ​​algún tipo de caso especial por alguna razón?

Porque estoy preguntando esto

  1. Propósitos educativos.
  2. Solía ​​realizar el método swizzling en una categoría dentro del objetivo de prueba de la unidad. Ahora, cuando la orden de llamada ha cambiado, applicationDidFinishLaunchingWithOptions se realiza antes de que se produzca el swizzling. Creo que hay otras formas de hacerlo, pero me parece que no es intuitivo. Funciona en Xcode 7. Pensé que cuando una clase se carga en la memoria, +load de esta clase y +load de todos los métodos. se supone que sus categorías deben llamarse antes de que podamos algo con esta clase (como crear una instancia y llamar didFinishLaunching... ).

TL, DR: Es culpa de xctest, no de objc.

Esto se debe a la forma en que el ejecutable xctest (el que en realidad ejecuta las pruebas unitarias, ubicado en $XCODE_DIR/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest carga su paquete.

Pre-Xcode 7, cargó todos los paquetes de prueba referenciados antes de ejecutar cualquier prueba. Esto se puede ver (para los que se preocupan), al desensamblar el binario para Xcode 6.4, se puede ver la sección correspondiente para el símbolo -[XCTestTool runTestFromBundle:] .

En la versión Xcode 7 de xctest , puede ver que demora la carga de paquetes de prueba hasta que XCTestSuite ejecuta la prueba real, en el marco XCTest real, que se puede ver en el símbolo __XCTestMain , que solo se invoca DESPUÉS del host de la prueba La aplicación está configurada.

Debido a que el orden de estos invocados cambió internamente, la forma en que se invocan los métodos de +load su prueba es diferente. No se realizaron cambios en los aspectos internos de object-c-runtime.

Si desea solucionar este problema en su aplicación, puede hacer algunas cosas. Primero, puedes cargar manualmente tu paquete usando +[NSBundle bundleWithPath:] , e invocar -load en eso.

También puede vincular su objetivo de prueba de nuevo a su aplicación de host de prueba (¡espero que esté usando un host de prueba diferente al de su aplicación principal!), Lo que haría que se cargue automáticamente cuando xctest cargue la aplicación de host.

No lo consideraría un error, es solo un detalle de implementación de XCTest.

Fuente: Simplemente pase los últimos 3 días desensamblando xctest por una razón completamente no relacionada.


Xcode 7 tiene dos órdenes de carga diferentes en el proyecto de plantilla de iOS.

Caso de prueba unitaria. Para Unit Test, el paquete de prueba se inyecta en la simulación de ejecución una vez que la aplicación se ha iniciado en la pantalla principal. La secuencia de ejecución predeterminada de Unit Test es como la siguiente:

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()

Caso de prueba UI. Para la prueba de UI, se configura un segundo proceso XCTRunner separado que ejerce la aplicación bajo prueba. Se puede pasar un argumento desde la setUp() prueba setUp() ...

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

... para ser recibido sea el 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 …

La inyección en el proceso separado se puede observar imprimiendo NSProcessInfo.processInfo().processIdentifier y NSProcessInfo.processInfo().processName desde tanto el código de prueba como el código de la aplicación.