Changed +load method order in Xcode 7
TL,DR: It's xctest's fault, not objc's.
This is because of how the xctest
executable (the one that actually runs the unit tests, located at $XCODE_DIR/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest
loads its bundle.
Pre-Xcode 7, it loaded all referenced test bundles before running any tests. This can be seen (for those that care), by disassembling the binary for Xcode 6.4, the relevant section can be seen for the symbol -[XCTestTool runTestFromBundle:]
.
In the Xcode 7 version of xctest
, you can see that it delays loading of testing bundles until the actual test is run by XCTestSuite
, in the actual XCTest
framework, which can be seen in the symbol __XCTestMain
, which is only invoked AFTER the test's host application is set-up.
Because the order of these being invoked internally changed, the way that your test's +load
methods are invoked is different. There were no changes made to the objective-c-runtime's internals.
If you want to fix this in your application, you can do a few things. First, you could manually load your bundle using +[NSBundle bundleWithPath:]
, and invoking -load
on that.
You could also link your test target back to your test host application (I hope you're using a separate test host than your main application!), which would make it be automatically loaded when xctest loads the host application.
I would not consider it a bug, it's just an implementation detail of XCTest.
Source: Just spend the last 3 days disassembling xctest
for a completely unrelated reason.
Xcode 7 has two different load orders in the iOS template project.
Unit Test Case. For Unit Test, the test bundle is injected into the running simulation after the application has launched through to the main screen. The default Unit Test execution sequence is like the following:
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 Test Case. For UI Test, a separate second process XCTRunner
is set up which exercises the application under test. An argument can be passed from the test setUp()
...
class Launch_UITests: XCTestCase {
override func setUp() {
// … other code …
let app = XCUIApplication()
app.launchArguments = ["UI_TESTING_MODE"]
app.launch()
// … other code …
}
... to be received be the 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 …
The injection into vs the separate process can be observed by printing NSProcessInfo.processInfo().processIdentifier
and NSProcessInfo.processInfo().processName
in from both the test code and the application code.