【问题标题】:Is it possible to test IBAction?是否可以测试 IBAction?
【发布时间】:2013-09-13 00:26:20
【问题描述】:

对 IBOutlets 进行单元测试有点容易,但是 IBActions 呢?我试图找到一种方法来做到这一点,但没有任何运气。有没有办法对视图控制器中的 IBAction 和 nib 文件中的按钮之间的连接进行单元测试?

【问题讨论】:

  • 你所说的“测试”是什么意思?你想为那些做单元测试吗?如果是这样,不仅仅是将您的操作封装到函数/类中并使用单元测试来测试这些类。如果您有其他想法 - 请再解释一下。
  • 是的,我要写单元测试。

标签: ios xctest ocunit


【解决方案1】:

@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
    }

【讨论】:

    【解决方案2】:

    对于完整的单元测试,每个出口/动作需要三个测试:

    1. 插座是否连接到视图?
    2. 插座是否连接到我们想要的操作?
    3. 直接调用操作,就好像它已被插座触发一样。

    我一直这样做是为了对视图控制器进行 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:"]);
    }
    

    【讨论】:

    • 我正在使用单元测试,所以如何使用 like-XCTAssertTrue 等检查此按钮是否被点击
    • 我在按钮上使用 segue,我该如何测试?
    • 为了做到这一点,您必须创建一个专门用于测试目的的IBOutlet。此外,您必须在字符串中引用处理程序,这意味着如果重构它将中断。
    • @jowie:AppCode 会自动重构类似的东西。但即使没有 AppCode,我也愿意付出小代价,因为我可以轻松快速地获得反馈。
    【解决方案3】:

    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
    }
    

    【讨论】:

      【解决方案4】:

      这是我在 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。我还需要传递包含按钮的控制器,以验证操作的目标。

      您还可以通过添加一些其他 else 子句使其更复杂一些,以防 unwrappedButton 不存在,这意味着插座不存在。因为我喜欢将出口和动作测试分开,所以这里没有包含它

      【讨论】:

      • 我为下面的 Swift 3 添加了这个翻译
      • 为 Swift 4 更新,感谢 Abbey 和 Julio
      【解决方案5】:

      这里已经有很多好的答案了。哪种方法最适合您取决于您​​的测试计划。

      既然这个问题已经变成了关于测试方法的调查,那么这里还有一个:如果你想测试操纵应用 UI 的结果,请查看UI Automation tool in Instruments

      【讨论】:

        【解决方案6】:

        我是用 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,则测试将失败并出现错误“未调用预期方法”。

        【讨论】:

        • 我似乎得到“没有调用预期的方法”,即使 IBOutlet 已连接。我什至在viewDidLoad 中有一个NSLog,它显示了对UIButton 的正确引用。
        【解决方案7】:

        因此,可能可以从情节提要或 nib 实例化视图控制器,然后在 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:
        

        【讨论】:

        • 这将检查逻辑,但不会检查 UIButton 是否实际上已在 Interface Builder 中连接。
        猜你喜欢
        • 2019-04-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-23
        • 2011-06-16
        • 2020-05-31
        • 2011-06-12
        • 1970-01-01
        相关资源
        最近更新 更多