Лесно е да тествате IBOutlets, но какво ще кажете за IBActions? Опитвах се да намеря начин как да го направя, но без успех. Има ли някакъв начин за единична тестова връзка между IBAction в View Controller и бутон в nib файла?
Възможно ли е да се тества 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 не съществува, което означава, че изходът не е там. Тъй като обичам да разделям изходите и тестовете за действие, не съм го включил тук
Отговорът на Julio Bailon по-горе е преведен за 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
}
Тук вече има много добри отговори. Кое работи най-добре за вас зависи от вашия план за тестване.
Тъй като този въпрос се превърна в анкета относно методите за тестване, ето още един: ако искате да тествате резултатите от манипулирането на потребителския интерфейс на вашето приложение, погледнете в Инструмент за автоматизация на потребителския интерфейс в Инструменти.