现在,我们触发 view 加载,并且模拟一行被点击:
  UIView *view = photosViewController.view; STAssertNotNil(view, @""); NSIndexPath* indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; [photosViewController tableView:photosViewController.tableView         didSelectRowAtIndexPath:indexPath];
  后我们验证 mocks 上期望的方法被调用过:
  [mockNavController verify]; [photosViewControllerMock verify];
  现在我们有了一个测试,用来测试和 navigation controller 的交互,以及正确 view controller 的创建。
  又一次地,我们在示例程序中使用了便捷的方法:
  - (id)autoVerifiedMockForClass:(Class)aClass; - (id)autoVerifiedPartialMockForObject:(id)object;
  于是,我们不需要记住调用 -verify。
  进一步探索
  像你从上面看到的那样,partial mocking 非常强大。如果你看看 -[PhotosViewController setupTableView] 方法的源码,你会看到它是如何从 app delegate 中取出 model 对象的。
  NSArray *photos = [AppDelegate sharedDelegate].store.sortedPhotos;
  上面的测试依赖于这行代码。打破这种依赖的一种方式是再次使用 partial mocking,让 app delegate 返回预定义的数据,像这样:
  id storeMock; // assume we've set this up id appDelegate = [AppDelegate sharedDelegate] id appDelegateMock = [OCMockObject partialMockForObject:appDelegate]; [[[appDelegateMock stub] andReturn:storeMock] store];
  现在,无论 [AppDelegate sharedDelegate].store 时候调用过,它也会返回 storeMock。可以把它发挥到。确保让你的测试尽可能保持简单,除非确实有复杂的需要。
  牢记的事
  Partial mocks 会修改 mocking 的对象,并且在 mocks 的生存期一直有效。你可以通过提前调用 [aMock stopMocking] 来停止这种行为。大多数时候,你希望 partial mock 在整个测试期间都保持有效。确保在测试方法后放置 [aMock verify]。否则 ARC 会过早 dealloc 这个 mock。而且不管怎样,你都希望加上 -verify。
  测试 NIB 加载
  PhotoCell 设置在一个 NIB 中,我们可以写一个简单的测试来检查 outlets 设置得是否正确。我们来回顾一下 PhotoCell 类:
  @interface PhotoCell : UITableViewCell  + (UINib *)nib;  @property (weak, nonatomic) IBOutlet UILabel* photoTitleLabel; @property (weak, nonatomic) IBOutlet UILabel* photoDateLabel;  @end
  我们的简单测试的实现看上去是这样:
  @implementation PhotoCellTests  - (void)testNibLoading; {     UINib *nib = [PhotoCell nib];     STAssertNotNil(nib, @"");      NSArray *a = [nib instantiateWithOwner:nil options:@{}];     STAssertEquals([a count], (NSUInteger) 1, @"");     PhotoCell *cell = a[0];     STAssertTrue([cell isMemberOfClass:[PhotoCell class]], @"");      // Check that outlets are set up correctly:     STAssertNotNil(cell.photoTitleLabel, @"");     STAssertNotNil(cell.photoDateLabel, @""); }  @end
  非常基础,但是它能工作。
  值得一提的是,当有什么发生变动时,测试和相应的类或 nib 需要同时更新。这是事实。你需要把它和 outlets 变化的可能性做权衡。如果你用了 .xib 文件,你可能要注意了,这是经常发生的事。
  关于 Class 和 Injection
  我们已经从与 Xcode 集成得知,测试 bundle 会注入到应用程序中。省略注入的如何工作的细节(它本身是个巨大的话题),简单地说:注入是把待注入的 bundle(我们的测试 bundle)中的 Objective-C 类添加到运行的应用程序中。这很好,因为这样允许我们运行测试了。
  还有一件事会很让人迷惑,那是如果我们同时把一个类添加到应用程序和测试 bundle中。如果在上面的示例程序中,(偶然)把 PhotoCell 类添加到测试 bundle 和应用程序,然后在测试 bundle 中调用 [PhotoCell class] 会返回一个不同的指针(你应用程序中的那个类)。于是我们的测试将会失败:
  STAssertTrue([cell isMemberOfClass:[PhotoCell class]], @"");
  再一次声明:注入很复杂。你应该避免:不要把应用程序中的 .m 文件添加到测试 target 中。否则你会得到预想不到的行为。
  额外的思考
  如果你使用一个持续集成的解决方案,让你的测试启动和运行是一个好主意。详细的描述超过了本文的范围。这些脚本通过 RunUnitTests 脚本触发。还有个 TEST_AFTER_BUILD 环境变量。
  一个有趣的选择是创建单独的测试 bundle 来自动化性能测试。你可以在测试方法里做任何你想做的。定时调用一些方法并使用 STAssert 来检查它们是否在特定阈值里面是一种选择。