Я пытаюсь включить тесты пользовательского интерфейса в свой проект iOS, но меня продолжает сдерживать тот факт, что кажется, что все тесты, которые вы пишете, должны начинаться с самого начала приложения и проходить через них. Например, если я хочу протестировать представление, которое находится за экраном входа в систему, мои тесты должны сначала запускаться на экране входа в систему, вводить имя пользователя / пароль, щелкнуть «Вход», а затем перейти к просмотру, который я хочу протестировать. В идеале тесты для просмотра входа в систему и следующего должны быть полностью изолированы. Есть ли способ сделать это, или мне полностью не хватает философии UI-тестов?
Тестирование пользовательского интерфейса iOS в изолированном виде
Ответы (3)
Абсолютно!
Что вам нужно, так это чистая среда приложений, в которой вы можете запускать свои тесты - чистый лист.
У всех приложений есть делегат приложения, который устанавливает начальное состояние приложения и предоставляет корневой контроллер представления при запуске. Для целей тестирования вы не хотите, чтобы это происходило - вам нужно иметь возможность тестировать изолированно, без того, чтобы все это происходило. В идеале вы хотите, чтобы экран был недотестирован и загружался только этот экран, и никаких других изменений состояния не происходило.
Для этого вы можете создать объект только для тестирования, который реализует UIApplicationDelegate
. Вы можете указать приложению работать в «режиме тестирования» и использовать делегат приложения для тестирования с помощью аргумента запуска.
Objective-C: main.m:
int main(int argc, char * argv[]) {
NSString * const kUITestingLaunchArgument = @"org.quellish.UITestingEnabled";
@autoreleasepool {
if ([[NSUserDefaults standardUserDefaults] valueForKey:kUITestingLaunchArgument] != nil){
return UIApplicationMain(argc, argv, nil, NSStringFromClass([TestingApplicationDelegate class]));
} else {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([ProductionApplicationDelegate class]));
}
}
}
Swift: main.swift:
let kUITestingLaunchArgument = "org.quellish.UITestingEnabled"
if (NSUserDefaults.standardUserDefaults().valueForKey(kUITestingLaunchArgument) != nil){
UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(UIApplication), NSStringFromClass(TestingApplicationDelegate))
} else {
UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(UIApplication), NSStringFromClass(AppDelegate))
}
Вам придется удалить любые @UIApplicationMain
аннотации из ваших классов Swift.
Для «тестов приложений» обязательно установите действие «Тест» схемы в Xcode, чтобы предоставить аргумент запуска:
Для тестов пользовательского интерфейса вы можете установить аргументы запуска как часть теста:
Цель-C:
XCUIApplication *app = [[XCUIApplication alloc] init];
[app setLaunchArguments:@[@"org.quellish.UITestingEnabled"] ];
[app launch];
Быстрый:
let app = XCUIApplication()
app.launchArguments = [ "org.quellish.UITestingEnabled" ]
app.launch()
Это позволяет тестам использовать делегат приложения специально для тестирования. Это дает вам большой контроль - теперь у вас есть чистый лист для тестирования. Делегат тестирующего приложения может загрузить конкретную раскадровку или вставить пустой UIViewController
. В рамках тестов пользовательского интерфейса вы можете создать экземпляр тестируемого контроллера представления и установить его в качестве корневого контроллера представления keyWindow
или представить его модально. После того, как он был добавлен или представлен, ваши тесты могут выполняться, а после его завершения удалить или отклонить его.
Если вы не возражаете против загрузки исходного пользовательского интерфейса, просто перейдите к целевому пользовательскому интерфейсу с помощью:
override func setUp() {
super.setUp()
continueAfterFailure = false
XCUIApplication().launch()
let storyboard = UIStoryboard(name: "MainStoryboard", bundle: NSBundle.mainBundle())
let controller = storyboard.instantiateViewControllerWithIdentifier("LanguageSelectController")
UIApplication.sharedApplication().keyWindow?.rootViewController = controller
}
Если вы не хотите, чтобы исходный пользовательский интерфейс внизу загружался, также передайте это из своего теста:
app.launchArguments.append("skipEntryViewController")
а затем в didFinishLaunchingWithOptions
вы можете проверить:
if NSProcessInfo.processInfo().arguments.contains("skipEntryViewController") {
// then do NOT call makeKeyAndVisible
}
К сожалению, при тестировании пользовательского интерфейса описанный вами сценарий невозможен.
Один из подходов, который я использую для борьбы с этим, - сгруппировать мои тесты в «потоки» функций. Например, предположим, что я хочу протестировать Feature A, Feature B и Feature C. Мне нужно войти в систему, чтобы все три работали.
Для каждого теста я не запускаю приложение, не авторизуюсь, а затем, наконец, запускаю сам тест. Вместо этого я запускаю приложение и один раз вхожу в систему. Затем я группирую свой тест в три частных вспомогательных метода: testFeatureA()
, testFeatureB()
и testFeatureC()
.
При создании единого потока выполнение набора тестов займет гораздо меньше времени. Большой недостаток заключается в том, что если функция A выйдет из строя, функция B никогда не будет протестирована. Этот подход следует использовать только в том случае, если вам важно, проходят ли все тесты или нет.
Бонусные баллы за использование помощника XCTest с параметрами __LINE__
и __FILE__
по умолчанию. Затем вы можете передать их в свои XCTFail()
вызовы, чтобы показать строку отказа на testFeatureA()
.