Изменен + порядок загрузки метода в Xcode 7
Я узнал, что Xcode 7 (версия 7.0 (7A220)) изменил порядок, в котором +load
методы для классов и категорий вызываются во время юнит-тестов.
Если категория, принадлежащая цели теста, реализует +load
метод, теперь он вызывается в конце, когда экземпляры класса, возможно, уже были созданы и использованы.
у меня есть AppDelegate
, который реализует +load
метод. AppDelegate.m
файл также содержит AppDelegate (MainModule)
категория. Кроме того, есть файл модульного теста LoadMethodTestTests.m
, который содержит другую категорию - AppDelegate (UnitTest)
,
Обе категории также реализуют +load
метод. Первая категория относится к основной цели, вторая - к контрольной цели.
Код
Я сделал небольшой тестовый проект, чтобы продемонстрировать проблему. Это пустой проект по умолчанию Xcode one view с измененными только двумя файлами.
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
И файл модульного теста (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
тестирование
Я выполнил модульное тестирование этого проекта (код и ссылка на github ниже) на Xcode 6/7 и получил следующее +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
Вопрос
Xcode 7 запускает целевую категорию теста +load
метод (Unit Test +load
) в конце концов, после AppDelegate
уже был создан.Это правильное поведение или это ошибка, которую следует отправить в Apple?
Может быть, он не указан, поэтому компилятор / среда выполнения могут свободно переставлять вызовы? Я посмотрел на этот вопрос SO, а также на описание +load в документации по NSObject, но я не совсем понял, как +load
Метод должен работать, когда категория принадлежит другой цели.
Или, может быть AppDelegate
это какой-то особый случай по какой-то причине?
Почему я спрашиваю это
- Образовательные цели.
- Я использовал метод swizzling в категории внутри цели модульного теста. Теперь, когда порядок звонков изменился,
applicationDidFinishLaunchingWithOptions
выполняется до того, как произойдет бурение. Я верю, что есть и другие способы сделать это, но мне просто кажется нелогичным, как это работает в Xcode 7. Я подумал, что когда класс загружается в память,+load
этого класса и+load
методы всех его категорий должны вызываться до того, как мы сможем что-то с этим классом (например, создать экземпляр и вызватьdidFinishLaunching...
).
2 ответа
TL,DR: это вина xctest, а не цель.
Это из-за того, как xctest
исполняемый файл (тот, который фактически запускает модульные тесты, расположенный в $XCODE_DIR/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest
загружает свою связку.
До Xcode 7 он загружал все упомянутые тестовые пакеты перед запуском любых тестов. Это можно увидеть (для тех, кто заботится), разобрав двоичный файл для Xcode 6.4, можно увидеть соответствующий раздел для символа -[XCTestTool runTestFromBundle:]
,
В версии Xcode 7 xctest
Вы можете видеть, что это задерживает загрузку тестовых пакетов до тех пор, пока XCTestSuite
в фактическом XCTest
рамки, которые можно увидеть в символе __XCTestMain
, который вызывается только ПОСЛЕ настройки хост-приложения теста.
Поскольку порядок их вызова внутренне изменился, способ, которым ваш тест +load
Методы вызываются иначе. Не было никаких изменений, внесенных во внутреннюю часть target-c-runtime.
Если вы хотите исправить это в своем приложении, вы можете сделать несколько вещей. Во-первых, вы можете вручную загрузить свой пакет, используя +[NSBundle bundleWithPath:]
и вызывая -load
на что.
Вы также можете связать свою тестовую цель с вашим тестовым хост-приложением (надеюсь, вы используете отдельный тестовый хост, чем ваше основное приложение!), Что сделает его автоматически загруженным, когда xctest загрузит хост-приложение.
Я бы не стал считать это ошибкой, это просто деталь реализации XCTest.
Источник: просто потратить последние 3 дня на разборку xctest
по совершенно не связанной причине.
Xcode 7 имеет два разных порядка загрузки в проекте шаблона iOS.
Модульный тест Для модульного теста тестовый пакет вводится в работающую симуляцию после запуска приложения на главном экране. Последовательность выполнения модульного теста по умолчанию выглядит следующим образом:
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. Для UI Test, отдельный второй процесс XCTRunner
устанавливается, который выполняет тестируемое приложение. Аргумент может быть передан из теста setUp()
...
class Launch_UITests: XCTestCase {
override func setUp() {
// … other code …
let app = XCUIApplication()
app.launchArguments = ["UI_TESTING_MODE"]
app.launch()
// … other code …
}
... быть принятым 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 …
Впрыск в отдельный процесс можно наблюдать, печатая NSProcessInfo.processInfo().processIdentifier
а также NSProcessInfo.processInfo().processName
как из кода теста, так и из кода приложения.