对IBOutlets进行单元测试有点容易,但IBActions呢?我试图找到一种方法,但没有任何运气。有没有办法在View Controller中的IBAction和nib文件中的按钮之间进行单元测试连接?
对于完整的单元测试,每个插座/操作需要三个测试:
我一直这样做TDD我的视图控制器。你可以看到一个例子in this screencast。
听起来你在具体询问第二步。这是一个单元测试的例子,用于验证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:"]);
}
我是用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,则测试将失败,并显示错误“未调用预期方法”。
这是我在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具有与事件TouchUpInside的名称scheduleOrder相关联的IBAction。我需要传递包含按钮的控制器,以便验证操作的目标。
你也可以通过添加一些其他的子句来使它变得更复杂,以防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上进行触摸。但是,我不会这样做,因为您正在测试Apple股票API。相反,我会通过直接调用该方法来测试。例如,如果你的视图控制器中有一个方法- (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:
@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
}
这里有很多好的答案。哪种方法最适合您,取决于您的测试计划。
由于这个问题已经变成了对测试方法的调查,还有一个问题:如果你想测试操作应用程序用户界面的结果,请查看UI Automation tool in Instruments。