Тестирование пользовательского интерфейса iOS в изолированном виде

Я пытаюсь включить тесты пользовательского интерфейса в свой проект iOS, но меня продолжает сдерживать тот факт, что кажется, что все тесты, которые вы пишете, должны начинаться с самого начала приложения и проходить через них. Например, если я хочу протестировать представление, которое находится за экраном входа в систему, мои тесты должны сначала запускаться на экране входа в систему, вводить имя пользователя / пароль, щелкнуть «Вход», а затем перейти к просмотру, который я хочу протестировать. В идеале тесты для просмотра входа в систему и следующего должны быть полностью изолированы. Есть ли способ сделать это, или мне полностью не хватает философии UI-тестов?


person mike    schedule 17.12.2015    source источник
comment
Я действительно очень хочу, чтобы это работало хорошо: приложение запускается с нуля, тестовый код решает, как представить VC, а тестовый код использует автоматизацию для взаимодействия с пользовательским интерфейсом. Это модульное тестирование пользовательского интерфейса. Все мои исследования по этой теме находятся здесь gist.github.com/fulldecent/529849bc5dd4464bbde2, возможно, кто-то еще можно подобрать факел.   -  person William Entriken    schedule 19.02.2016


Ответы (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, чтобы предоставить аргумент запуска:

Редактор схемы 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 или представить его модально. После того, как он был добавлен или представлен, ваши тесты могут выполняться, а после его завершения удалить или отклонить его.

person quellish    schedule 18.12.2015
comment
Это решение кажется действительно хорошим! Однако NSUserDefaults.standardUserDefaults (). ValueForKey (kUITestingLaunchArgument)! = Nil у меня не сработал. Я изменил его на NSProcessInfo.processInfo (). Arguments.contains (kUITestingLaunchArgument), чтобы он работал. - person Fyodor Volchyok; 28.01.2016
comment
@FyodorVolchyok Я настоятельно рекомендую зарегистрировать радар, так как пользовательские настройки по умолчанию должны работать. Аргументы запуска заменяют другие значения по умолчанию, поскольку они находятся в области аргументов. - person quellish; 29.01.2016
comment
@quellish Вы не можете легко загрузить UIStoryboard в тесте (если вы не предоставите частную переменную, как в своем сообщении quellish.tumblr.com/post/135415677047/). Я использую дополнительные аргументы, чтобы TestAppDelegate знал, что UIStoryboard и UIViewController нужно создать. - person Marco Mustapic; 15.12.2016

Если вы не возражаете против загрузки исходного пользовательского интерфейса, просто перейдите к целевому пользовательскому интерфейсу с помощью:

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
}
person William Entriken    schedule 13.01.2016
comment
В вопросе указано, что необходимо чистое и изолированное тестирование, а это не так. - person Pierre Mardon; 03.12.2016
comment
Как это не соответствует этому требованию? - person William Entriken; 03.12.2016
comment
Сохранение того же делегата приложения и исходной загрузки пользовательского интерфейса может сделать некоторую работу, которая может помешать. - person Pierre Mardon; 03.12.2016
comment
На [UIApplication sharedApplication] .keyWindow.rootViewController я не понимаю, почему? - person magofdl; 04.02.2017
comment
Когда я пытаюсь создать экземпляр раскадровки с помощью UIStoryboard (имя: Main, bundle: Bundle.main), приложение дает сбой, и тест не проходит, не давая мне обратной связи. Любые идеи? - person jotaEsse; 05.05.2017
comment
@jotaEsse, потому что загруженный пакет является пакетом тестирования, а не пакетом приложения. Я тоже застрял в этом месте. Вы когда-нибудь находили обходной путь? - person James Webster; 24.08.2017
comment
Невозможно найти раскадровку даже с правильным названием ??? Для этого нужно что-нибудь импортировать ?? - person Muhammad Umer Farooq; 21.11.2017

К сожалению, при тестировании пользовательского интерфейса описанный вами сценарий невозможен.

Один из подходов, который я использую для борьбы с этим, - сгруппировать мои тесты в «потоки» функций. Например, предположим, что я хочу протестировать Feature A, Feature B и Feature C. Мне нужно войти в систему, чтобы все три работали.

Для каждого теста я не запускаю приложение, не авторизуюсь, а затем, наконец, запускаю сам тест. Вместо этого я запускаю приложение и один раз вхожу в систему. Затем я группирую свой тест в три частных вспомогательных метода: testFeatureA(), testFeatureB() и testFeatureC().

При создании единого потока выполнение набора тестов займет гораздо меньше времени. Большой недостаток заключается в том, что если функция A выйдет из строя, функция B никогда не будет протестирована. Этот подход следует использовать только в том случае, если вам важно, проходят ли все тесты или нет.

Бонусные баллы за использование помощника XCTest с параметрами __LINE__ и __FILE__ по умолчанию. Затем вы можете передать их в свои XCTFail() вызовы, чтобы показать строку отказа на testFeatureA().

person Joe Masilotti    schedule 17.12.2015
comment
Проголосовали против, поскольку это действительно возможно (см. Мягкий ответ) - person Pierre Mardon; 03.12.2016