测试 Data Source
  好了,让我们开始吧。我们已经通过拆分 view controller 让测试工作变得更轻松了。现在我们要测试 ArrayDataSource。首先我们新建一个空的,基本的测试类。我们把接口和实现都放到一个文件里;也没有哪个地方需要包含 @interface ,放到一个文件会显得更加漂亮和整洁。
  #import "PhotoDataTestCase.h"  @interface ArrayDataSourceTest : PhotoDataTestCase @end  @implementation ArrayDataSourceTest - (void)testNothing; {     STAssertTrue(YES, @""); } @end
  这个类没做什么事,只是展示了基本的设置。当我们运行这个测试时,-testNothing 方法将会运行。特别地,STAssert 宏将会做琐碎的检查。注意,前缀 ST 源自于 SenTestingKit。这些宏和 Xcode 集成,会把失败显示到 Issues navigator 中。
  第一个测试
  我们现在把 testNothing 替换成一个简单、真正的测试:
- (void)testInitializing; {     STAssertNil([[ArrayDataSource alloc] init], @"Should not be allowed.");     TableViewCellConfigureBlock block = ^(UITableViewCell *a, id b){};     id obj1 = [[ArrayDataSource alloc] initWithItems:@[]                                       cellIdentifier:@"foo"                                   configureCellBlock:block];     STAssertNotNil(obj1, @""); }
  实践 Mocking
  接着,我们想测试 ArrayDataSource 实现的方法:
  - (UITableViewCell *)tableView:(UITableView *)tableView          cellForRowAtIndexPath:(NSIndexPath *)indexPath;
  为此,我们创建一个测试方法:
  - (void)testCellConfiguration;
  首先,创建一个 data source:
__block UITableViewCell *configuredCell = nil; __block id configuredObject = nil; TableViewCellConfigureBlock block = ^(UITableViewCell *a, id b){     configuredCell = a;     configuredObject = b; }; ArrayDataSource *dataSource = [[ArrayDataSource alloc] initWithItems:@[@"a", @"b"]                                                       cellIdentifier:@"foo"                                                   configureCellBlock:block];
  注意,configureCellBlock 除了存储对象以外什么都没做,这可以让我们可以更简单地测试它。
  然后,我们为 table view 创建一个 mock 对象:
  id mockTableView = [OCMockObject mockForClass:[UITableView class]];
  Data source 将在传进来的 table view 上调用 -dequeueReusableCellWithIdentifier:forIndexPath: 方法。我们将告诉 mock object 当它收到这个消息时要做什么。首先创建一个 cell,然后设置 mock。
  UITableViewCell *cell = [[UITableViewCell alloc] init]; NSIndexPath* indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; [[[mockTableView expect] andReturn:cell]         dequeueReusableCellWithIdentifier:@"foo"                              forIndexPath:indexPath];
  第一次看到它可能会觉得有点迷惑。我们在这里所做的,是让 mock 记录特定的调用。Mock 不是一个真正的 table view;我们只是假装它是。-expect 方法允许我们设置一个 mock,让它知道当这个方法调用时要做什么。
  另外,-expect 方法也告诉 mock 这个调用必须发生。当我们稍后在 mock 上调用 -verify 时,如果那个方法没有被调用过,测试会失败。相应地,-stub 方法也用来设置 mock 对象,但它不关心方法是否被调用过。
  现在,我们要触发代码运行。我们调用我们希望测试的方法。
  NSIndexPath* indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; id result = [dataSource tableView:mockTableView             cellForRowAtIndexPath:indexPath];
  然后我们测试是否一切正常:
  STAssertEquals(result, cell, @"Should return the dummy cell."); STAssertEquals(configuredCell, cell, @"This should have been passed to the block."); STAssertEqualObjects(configuredObject, @"a", @"This should have been passed to the block."); [mockTableView verify];
  STAssert 宏测试值的相等性。注意,前两个测试,我们通过比较指针来完成;我们不想使用 -isEqual:。我们实际希望测试的是 result,cell 和 configuredCell 都是同一个对象。第三个测试要用 -isEqual:,后我们调用 mock 的 -verify 方法。
  注意,在示例程序中,我们是这样设置 mock 的:
  id mockTableView = [self autoVerifiedMockForClass:[UITableView class]];
  这是我们测试基类中的一个方便的封装,它会在测试后自动调用 -verify 方法。
  测试 UITableViewController
  下面,我们转向 PhotosViewController。它是个 UITableViewController 的子类,它使用了我们刚才测试过的 data source。View controller 剩下的代码已经相当简单了。
  我们想测试点击 cell 后把我们带到详情页面,即一个 PhotoViewController 的实例被 push 到 navigation controller 里面。我们再次使用 mocking 来让测试尽可能不依赖于其他部分。
  首先我们创建一个 UINavigationController 的 mock:
  id mockNavController = [OCMockObject mockForClass:[UINavigationController class]];
  接下来,我们要使用 partial mocking。我们希望 PhotosViewController 实例的 navigationController 返回 mockNavController。我们不能直接设置 navigation controller,所以我们简单地对 PhotosViewController 实例 stub 这个方法,让它返回 mockNavController 可以了。
  PhotosViewController *photosViewController = [[PhotosViewController alloc] init]; id photosViewControllerMock = [OCMockObject partialMockForObject:photosViewController]; [[[photosViewControllerMock stub] andReturn:mockNavController] navigationController];
  现在,任何时候对 photosViewController 调用 -navigationController 方法,都会返回 mockNavController。这是个强大的技巧,OCMock 有这种本领。
  现在,我们要告诉 navigation controller mock 我们调用的期望,即,一个 photo 不为 nil 的 detail view controller。
  UIViewController* viewController = [OCMArg checkWithBlock:^BOOL(id obj) {     PhotoViewController *vc = obj;     return ([vc isKindOfClass:[PhotoViewController class]] &&             (vc.photo != nil)); }]; [[mockNavController expect] pushViewController:viewController animated:YES];