Модульное тестирование IBOutlets довольно просто, но как насчет IBActions? Я пытался найти способ, как это сделать, но безуспешно. Есть ли способ установить модульное тестовое соединение между IBAction в контроллере представления и кнопкой в файле пера?
Можно ли протестировать IBAction?
Ответы (7)
Для полного модульного тестирования для каждого выхода/действия требуется три теста:
- Розетка подключена к виду?
- Связана ли розетка с действием, которое мы хотим?
- Вызовите действие напрямую, как если бы оно было запущено розеткой.
Я делаю это все время, чтобы TDD мои контроллеры представления. Вы можете увидеть пример в этом скринкасте.
Похоже, вы спрашиваете конкретно о втором шаге. Вот пример модульного теста, подтверждающего, что прикосновение внутри myButton
вызовет действие doSomething:
Вот как я выражаю это, используя OCHamcrest а>. (sut
— тестовое приспособление для тестируемой системы.)
- (void)testMyButtonAction {
assertThat([sut.myButton actionsForTarget:sut
forControlEvent:UIControlEventTouchUpInside],
contains(@"doSomething:", nil));
}
В качестве альтернативы, вот версия без Hamcrest:
- (void)testMyButtonAction {
NSArray *actions = [sut.myButton actionsForTarget:sut
forControlEvent:UIControlEventTouchUpInside];
XCTAssertTrue([actions containsObject:@"doSomething:"]);
}
IBOutlet
специально для целей тестирования. Также вы должны ссылаться на обработчик в строке, что означает, что он сломается при рефакторинге.
- person jowie; 08.12.2014
Я сделал это с помощью OCMock, вот так:
MyViewController *mainView = [[MyViewController alloc] initWithNibName:@"MyViewController" bundle:nil];
[mainView view];
id mock = [OCMockObject partialMockForObject:mainView];
//testButtonPressed IBAction should be triggered
[[mock expect] testButtonPressed:[OCMArg any]];
//simulate button press
[mainView.testButton sendActionsForControlEvents: UIControlEventTouchUpInside];
[mock verify];
Если IBAction не подключен, тест завершится с ошибкой "ожидаемый метод не был вызван".
NSLog
в viewDidLoad
, который отображает правильную ссылку на UIButton
.
- person jowie; 08.12.2014
Вот что я использую в Swift. Я создал вспомогательную функцию, которую могу использовать во всех своих модульных тестах UIViewController:
func checkActionForOutlet(outlet: UIButton?, actionName: String, event: UIControlEvents, controller: UIViewController)->Bool{
if let unwrappedButton = outlet {
if let actions: [String] = unwrappedButton.actionsForTarget(controller, forControlEvent: event)! as [String] {
return(actions.contains(actionName))
}
}
return false
}
И затем я просто вызываю его из теста следующим образом:
func testScheduleActionIsConnected() {
XCTAssertTrue(checkActionForOutlet(controller.btnScheduleOrder, actionName: "scheduleOrder", event: UIControlEvents.TouchUpInside, controller: controller ))
}
Я в основном удостоверяюсь, что кнопка btnScheduleOrder имеет IBAction, связанную с именем scheduleOrder для события TouchUpInside. Мне нужно передать контроллер, в котором находится кнопка, чтобы также проверить цель действия.
Вы также можете сделать его немного более сложным, добавив какое-то другое предложение else на случай, если unwrappedButton не существует, что означает, что выхода нет. Поскольку мне нравится разделять тесты розеток и действий, я не включил их сюда.
Ответ Хулио Бейлона выше, переведенный для Swift 3:
func checkActionForButton(_ button: UIButton?, actionName: String, event: UIControlEvents = UIControlEvents.touchUpInside, target: UIViewController) -> Bool {
if let unwrappedButton = button, let actions = unwrappedButton.actions(forTarget: target, forControlEvent: event) {
var testAction = actionName
if let trimmedActionName = actionName.components(separatedBy: ":").first {
testAction = trimmedActionName
}
return (!actions.filter { $0.contains(testAction) }.isEmpty)
}
return false
}
Таким образом, вероятно, можно создать экземпляр контроллера представления из раскадровки или пера, а затем выполнить касание UIButton. Однако я бы не стал этого делать, потому что вы тестируете стандартный API Apple. Скорее, я бы проверил, напрямую вызвав метод. Например, если у вас есть метод - (IBAction)foo:(id)sender
в вашем контроллере представления, и вам нужно проверить логику в этом методе, я бы сделал что-то вроде этого:
MyViewController *viewController = [[MyViewController alloc] initWithNibName:@"NibName" bundle:[NSBundle mainBundle]];
UIButton *sampleButton = [[UIButton alloc] init];
[sampleButton setTitle:@"Default Storyboard Title" forState:UIControlStateNormal];
[viewController foo:sampleButton];
// Run asserts now on the logic in foo:
UIButton
подключен в Interface Builder.
- person jowie; 08.12.2014
Версия @AbbeyJackson Swift 3 обновлена до Swift 4.2, спасибо @JulioBailon за исходную версию.
func checkActionForButton(_ button: UIButton?, actionName: String, event: UIControl.Event = UIControl.Event.touchUpInside, target: UIViewController) -> Bool {
if let unwrappedButton = button, let actions = unwrappedButton.actions(forTarget: target, forControlEvent: event) {
var testAction = actionName
if let trimmedActionName = actionName.components(separatedBy: ":").first {
testAction = trimmedActionName
}
return (!actions.filter { $0.contains(testAction) }.isEmpty)
}
return false
}
Здесь уже много хороших ответов. Что лучше всего подходит для вас, зависит от вашего плана тестирования.
Поскольку этот вопрос превратился в обзор методов тестирования, вот еще один: если вы хотите проверить результаты манипулирования пользовательским интерфейсом вашего приложения, загляните в Инструмент автоматизации пользовательского интерфейса в инструментах.