Ios 测试是否在视图控制器方法内调用PerformsgueWithIdentifier

Ios 测试是否在视图控制器方法内调用PerformsgueWithIdentifier,ios,objective-c,unit-testing,ocmock,xctest,Ios,Objective C,Unit Testing,Ocmock,Xctest,我正在处理一个应用程序并添加单元测试。该应用程序使用故事板编写,支持iOS 6.1及以上版本 我能够毫无问题地测试所有常用的返回方法。但是,我目前遇到了一个我想执行的特定测试: 基本上我有一个方法,我们称之为doLogin: - (IBAction)doLogin:(UIButton *)sender { // Some logic here if ( //certain criteria to meet) { variable = x; // important variable

我正在处理一个应用程序并添加单元测试。该应用程序使用故事板编写,支持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方法中设置的变量在prepareforsgue方法中传递给memorableWord segues的目标视图控制器

我已经建立了OCMock并开始工作,我还使用XCTest作为我的单元测试框架。有没有人能够生产一个单元测试来覆盖这种情况


谷歌和其他公司似乎对这一领域的信息一无所知。。关于简单基本测试的大量示例与更复杂的iOS测试事实无关。

IMHO这似乎是一个未正确设置的测试场景

使用单元测试时,您应该只测试应用程序的
单元
(例如单个方法)。这些单元应该独立于应用程序的所有其他部分。这将保证您可以正确测试单个功能,而不会产生任何副作用。 顺便说一句:
OCMock
是一个很好的工具,可以“模拟”所有不想测试的部件,从而产生副作用

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

这是软件测试的一个阶段,在这个阶段中,单个软件模块作为一个组进行组合和测试

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

我可以定义一个集成测试,在这个测试中,我可以正确地测试视图的所有部分,从而间接地测试视图控制器。请看一看这类场景的良好测试框架-


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

因此,我能看到的唯一进行单元测试的方法是使用部分模拟:

- (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];
}

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

如果您的思路正确,您的测试将检查:

  • 当点击login按钮时,以loginButton作为发送方调用doLogin
  • 如果某些条件为“是”,请调用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];
    }
    
    您需要为“criteria”实现一个属性,以便可以使用“expect”模拟一个getter


    重要的是要认识到“expect”将只模拟对getter的一个调用,后续调用将以“意外调用的方法…”失败。您可以使用“stub”模拟所有调用,但这意味着它将始终返回相同的值。

    请阅读OCMock,我刚从amazon上买了一本关于单元测试iOS的书,这本书非常好读。我也想买一本TDD书。

    谢谢,但这正是我想要做的。我试图在不依赖任何其他东西的情况下,单独测试doLogin方法。例如,作为我的一个测试,我希望确保当满足条件时,该方法将实际调用performsguewithidentifier。我还将测试错误是否得到正确处理。我的问题是确定天气与否。是否调用performSegueWithIdentifier。。对此有什么想法吗?+1对于IT和UT之间的区别,我相信不太熟悉这一概念的用户会发现它很有用!:)OCMock确实有一个类似于verify或reject的方法来实现这种目的。使用verify可以确保方法已被调用,通过reject可以确保方法未被调用。请记住,在您的情况下,您可能需要使用部分模拟,并且使用许多部分模拟可能是应用程序设计不好的标志。您发表评论时,我只是在实现部分模拟,基本上这就是我所缺少的。我不确定如果代码中调用了segue而没有它,您还将如何测试,因此我假设任何使用此方法在控制器之间转换的人,如果想用单元测试覆盖此代码,都会有相当多的部分模拟?