segues的自动化测试

Posted

技术标签:

【中文标题】segues的自动化测试【英文标题】:Automated testing of segues 【发布时间】:2013-06-23 15:43:09 【问题描述】:

我想创建一个集成测试,显示某个操作会导致显示模态视图控制器。故事板设置有 2 个视图控制器,一个具有自定义 ViewController 类,第二个具有默认 UIViewController 类和标题“second”。 segue 设置为具有标识符“modalsegue”的模态。在模拟器中运行应用程序效果很好,但我在定义正确的测试时遇到了很多麻烦。

ViewController.m:

@implementation ViewController

- (IBAction)handleActionByPerformingModalSegue 
    [self performSegueWithIdentifier:@"modalsegue" sender:self];

@end

测试:

- (void)testActionCausesDisplayOfSecondViewController 
    ViewController * vc =
      [[UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil]   
          instantiateViewControllerWithIdentifier:@"ViewController"];

    [vc handleActionByPerformingModalSegue];
    STAssertEquals(vc.presentedViewController.title, @"second",
        @"Title of presented view controller should be second but is %@",   
        vc.presentedViewController.title, nil);

在以下输出中运行测试结果:

2013-06-23 17:38:44.164 SeguesRUs[15291:c07] Warning: Attempt to present <UIViewController: 0x7561370> on <ViewController: 0x7566590> whose view is not in the window hierarchy!
SeguesRUsTests.m:33: error: -[SeguesRUsTests testActionCausesDisplayOfSecondViewController] : '<00000000>' should be equal to '<9c210d07>': Title of presented view controller should be second but is (null)

我做错了什么?有没有一种简单的方法可以避免第一条消息?

【问题讨论】:

好吧,您可能应该切换到更高级别的框架,该框架将实际测试 UI 而不是一些内部方法 - 例如 Apple 的 frank-cucumber 的 UI 自动化。 【参考方案1】:

正如错误消息指出的那样,问题在于您试图在 UIViewController 上呈现,其视图不在 UIWindow 层次结构中。

最简单的修复方法:

- (void)testExample 

    //
    // Arrange
    // Storyboard
    //
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];

    //
    // Arrange
    // View Controller
    //
    UIViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:@"ViewController"];
    [UIApplication sharedApplication].keyWindow.rootViewController = viewController;

    //
    // Act
    //
    [viewController performSegueWithIdentifier:@"ModalSegue" sender:nil];

    //
    // Assert
    //
    XCTAssertEqualObjects(viewController.presentedViewController.title, @"Second");


【讨论】:

[UIApplication sharedApplication] 不是要在单元测试中初始化 UIApplication 的实例吗?那里会发生什么? 我正在使用UIApplication 的共享实例来访问keyWindow,以便我可以设置它的rootViewController【参考方案2】:

这就是我所做的。 假设我连接了带有手动触发 segue (DocumentsDetailVC) 的 DocumentsVC。 下面是我的设置,然后我测试 1. segue 的存在,然后 2. 我强制视图控制器(在我的情况下我发布通知)触发其 performSegueWithIdentifier 并拦截 prepareForSegue 方法以查看是否所有新的视图控制器(DocumentsDetailVC)已设置。这包括方法调配。

我没有使用 OCHamcrest/OCMockito 进行单元测试,并且我所有的 segue 都以“Segue”添加的目标视图控制器命名([self appDelegate] segueIdentifierForClass:[SomeClass class]])。

- (void)setUp

  [super setUp];

  _isPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;

  realPrepareForSegue = @selector(prepareForSegue:sender:);
  testPrepareForSegue = @selector(documentsBrowserTest_prepareForSegue:sender:);

  UIStoryboard *storyboard = nil;
  if (_isPad) 
    storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard_iPhone" bundle:nil];
  
  else 
    storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard_iPad" bundle:nil];
  
  UINavigationController *navController = [storyboard instantiateInitialViewController];
  self.sut = (DocumentsBrowserVC *)navController.topViewController;
  [self.sut view];



- (void)test_DocumentsDetailsVCSegueConnected

  if (_isPad == FALSE) 
    STAssertNoThrow([self.sut performSegueWithIdentifier:[[self appDelegate] segueIdentifierForClass:[DocumentsDetailVC class]] sender:self], @"DocumentsDetailVC should be connected");
  
 


- (void)test_providerDidSelectPathLevelObject_triggersDocumentsDetailsVCSegueSectionIdFile

  [DocumentsBrowserTest swapInstanceMethodsForClass:[DocumentsBrowserVC class]
                                                selector:realPrepareForSegue
                                             andSelector:testPrepareForSegue];

  [[NSNotificationCenter defaultCenter] addObserver:self.sut selector:@selector(providerDidSelectPathLevelObject:) name:ProviderDidSelectPathLevelObjectNotification object:nil];

  // when    
PathLevelObject *plo = self.pathLevelObjects[SectionIdFile][4];
NSDictionary *userInfo = @OBJECT_KEY : plo , BROWSER_AREA_KEY : @(DocumentsFolder);
[[NSNotificationCenter defaultCenter] postNotificationName:ProviderDidSelectPathLevelObjectNotification object:nil userInfo:userInfo];

  // then
  if (_isPad == FALSE) 
    assertThat(NSStringFromClass([objc_getAssociatedObject(self.sut, storyboardSegueKey) class]), is(equalTo(@"UIStoryboardPushSegue")));
    assertThatBool([[objc_getAssociatedObject(self.sut, storyboardSegueKey) destinationViewController] isKindOfClass:[DocumentsDetailVC class]], is(equalToBool(TRUE)));
    assertThat(objc_getAssociatedObject(self.sut, senderKey), is(equalTo(self.sut)));
  
  else 
    assertThatInteger(self.sut.detailViewController.browsingArea, is(equalToInteger(DocumentsFolder)));
    assertThat(self.sut.detailViewController.pathLevelObject, is(equalTo(plo)));
  


  [[NSNotificationCenter defaultCenter] removeObserver:self.sut];

  [DocumentsBrowserTest swapInstanceMethodsForClass:[DocumentsBrowserVC class]
                                                selector:realPrepareForSegue
                                             andSelector:testPrepareForSegue];

【讨论】:

这是很多测试代码,用于验证相对较少。你会说你从这样的测试中获得了很多价值吗?

以上是关于segues的自动化测试的主要内容,如果未能解决你的问题,请参考以下文章

segue 在简单的 segue 测试中不起作用

单元测试:ViewController 没有带有标识符的 segue:“segue id”

XCTest 如何执行 segue 呈现模态视图并测试presentedViewController

使用计算的返回值进行单元测试展开 segue

在单元测试中以编程方式执行 Unwind Segue

自动化测试都包含哪些内容?