【问题标题】:Testing if performSegueWithIdentifier is called within a view controllers method测试是否在视图控制器方法中调用 performSegueWithIdentifier
【发布时间】:2014-01-03 17:55:51
【问题描述】:

我正在浏览一个应用程序并添加单元测试。该应用程序使用故事板编写,支持 iOS 6.1 及更高版本。

我已经能够毫无问题地测试所有常用的返回方法。但是,我目前对要执行的某个测试感到困惑:

基本上我有一个方法,我们称之为doLogin:

- (IBAction)doLogin:(UIButton *)sender {

// Some logic here

if ( //certain criteria to meet) {
    variable = x; // important variable set here
    [self performSegueWithIdentifier:@"memorableWord" sender:sender];
} else {
    // handler error here
}

所以我想测试是否调用了 segue 并设置了变量,或者是否加载了 MemorableWord 视图控制器并且其中的变量是否正确。在 doLogin 方法中设置的变量在 prepareForSegue 方法中被传递给 rememberWord segues 的目标视图控制器。

我已经设置并运行了 OCMock,并且我还使用 XCTest 作为我的单元测试框架。有没有人能够制作一个单元测试来涵盖这种情况??

Google 和 SO 似乎在这方面的信息方面相当少见。许多关于简单基本测试的示例与更复杂的 iOS 测试现实完全无关。

【问题讨论】:

    标签: ios objective-c unit-testing ocmock xctest


    【解决方案1】:

    读一读 OCMock,我刚从亚马逊买了一本关于 iOS 单元测试的书,它真的很好读。也想买一本 TDD 书。

    【讨论】:

      【解决方案2】:

      你在正确的轨道上,你的测试想要检查:

      1. 当登录按钮被点击时,doLogin 被调用,loginButton 作为发送者
      2. 如果某些条件为 YES,则调用 performSegue

      所以你实际上应该触发从登录按钮到 performSegue 的整个流程:

      - (void)testLogin {
          LoginViewController *loginViewController = ...;
          id loginMock = [OCMockObject partialMockForObject:loginViewController];
      
          //here the expect call has the advantage of swallowing performSegueWithIdentifier, you can use forwardToRealObject to get it to go all the way through if necessary
          [[loginMock expect] performSegueWithIdentifier:@"memorableWord" sender:loginViewController.loginButton];
      
          //you also expect this action to be called
          [[loginMock expect] doLogin:loginViewController.loginButton];
      
          //mocking out the criteria to get through the if statement can happen on the partial mock as well
          BOOL doSegue = YES;
          [[[loginMock expect] andReturnValue:OCMOCK_VALUE(doSegue)] criteria];
      
          [loginViewController.loginButton sendActionsForControlEvents:UIControlEventTouchUpInside];
      
          [loginMock verify]; [loginMock stopMocking];
      }
      

      您需要为“条件”实现一个属性,以便您可以使用“期望”模拟一个 getter。

      重要的是要意识到'expect'只会模拟1次对getter的调用,后续调用将失败并显示“调用了意外的方法......”。您可以使用“存根”为所有调用模拟它,但这意味着它将始终返回相同的值。

      【讨论】:

        【解决方案3】:

        所以我能看到对我进行单元测试的唯一方法是使用部分模拟:

        - (void)testExample
        {
            id loginMock = [OCMockObject partialMockForObject:self.controller];
        
            [[loginMock expect] performSegueWithIdentifier:@"memorableWord" sender:[OCMArg any]];
        
            [loginMock performSelectorOnMainThread:@selector(loginButton:) withObject:self.controller.loginButton waitUntilDone:YES];
        
            [loginMock verify];
        }
        

        当然,这只是测试的一个示例,实际上并不是我正在执行的测试,但希望能演示我必须在视图控制器中测试此方法的方式。如您所见,如果未调用performSegueWithIdentifier,则verify with 会导致测试失败。

        【讨论】:

          【解决方案4】:

          恕我直言,这似乎是一个已设置 not properly 的测试场景。

          使用单元测试,您应该只对您的应用程序进行test units(例如单一方法)。这些单位应该是来自应用程序所有其他部分的independent。这将保证您正确测试单个功能而没有任何副作用。 顺便说一句:OCMock 是“模拟”您不想测试的所有部分并因此产生副作用的好工具。

          一般来说,您的测试似乎更像是一个集成测试

          IT is the phase of software testing, in which individual software modules are combined and tested as a group

          那么在你的情况下我会怎么做:

          我会定义一个集成测试,在那里我会正确测试我的视图的所有部分,从而间接测试我的视图控制器。看看这种场景的一个很好的测试框架 - KIF

          或者我将对方法“doLogin”以及计算 if 语句中的条件的方法执行单个单元测试。所有依赖项都应该被模拟出来,这意味着在你的 doLogin 测试中,你甚至应该模拟标准方法......

          【讨论】:

          • 谢谢,但这正是我想要做的。我正在尝试自行测试 doLogin 方法,而不依赖其他任何东西。因此,例如,作为我的测试之一,我想确保该方法在满足条件时实际调用 performSegueWithIdentifier。我还将测试错误是否得到了正确处理。我的问题是找出天气或不调用 performSegueWithIdentifier .. 对此有什么想法吗?
          • +1 对 IT 和 UT 之间区别的体面解释,我相信不太熟悉这个概念的用户会发现它很有用! :)
          • OCMock 确实有一种方法,例如验证或拒绝完全用于这种目的。通过验证,您可以确保方法已被调用,通过拒绝,您可以确保方法未被调用。请记住,在您的情况下,您可能需要使用部分模拟,并且使用许多部分模拟可能是糟糕的应用程序设计的标志
          • 当您发表评论时,我只是在实现部分模拟,基本上这就是我所缺少的。我不确定如果没有这个,如果在代码中调用 segue,你将如何测试,所以我假设任何使用这种方法在控制器之间转换,想要用单元测试覆盖这个代码的人,都会有一个公平的有几个部分模拟?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-05-19
          • 1970-01-01
          • 1970-01-01
          • 2022-08-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多