Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing with Snapshots

Testing with Snapshots

A talk I gave at CocoaHeads Berlin about testing iOS view components with FBSnapshotTestCase

Avatar for Philip Brechler

Philip Brechler

June 17, 2015

Other Decks in Technology

Transcript

  1. About me • iOS developer since 2008 • Mobile Lead

    MOTOR-TALK.de • Developing apps for Europe’s biggest automotive community
  2. Testing Mobile Apps • Apps are getting more complex •

    Testing is essential for great quality • Testing logic with unit tests • Testing UI with UI Testing Frameworks (KIF etc.)
  3. Problems when Testing • Logic tests are fast and easy

    to write but test only a bare minimal • UI tests are slow and complex to write • Tests should encourage a good architecture
  4. Testing a Cell • Testing if a cell’s layout is

    correct is really hard • Testing landscape and portrait • Testing edge cases • Testing states • Testing on a lot of devices
  5. FBSnapshotTestCase • OpenSource Project by Jonathan Dan (Facebook) • Take

    reference pictures from view components • Compare those pictures to current state
  6. - (void)testWithViewModel { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];

    ThreadTableViewController *tableViewController = [storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([ThreadTableVie wController class])]; PostTableViewCell *cell = [tableViewController.tableView dequeueReusableCellWithIdentifier:NSStringFromClass([PostTableViewCell class])]; Post *testPost = [[Post alloc]init]; [testPost setTimestamp:[NSDate dateWithTimeIntervalSince1970:1420120800]]; [testPost setAuthor:[[User alloc] init]]; testPost.author.displayName = @"Test User"; testPost.text = @"Jujubes pie wafer (…)“; CGFloat heightForCell = [PostTableViewCell heightForPostText:testPost.text cellWidth:320]; [cell setFrame:CGRectMake(0, 0, 320, heightForCell)]; PostViewModel *viewModel = [[PostViewModel alloc]initWithPost:testPost]; cell.authorNameLabel.text = viewModel.authorName; cell.authorAvatarImageView.image = [UIImage imageNamed:@"no_avatar"] ; cell.postTextView.attributedText = viewModel.text; cell.postDateLabel.text = viewModel.dateString; FBSnapshotVerifyView(cell, nil); }
  7. - (void)testWithViewModel { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];

    ThreadTableViewController *tableViewController = [storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([ThreadTableVie wController class])]; PostTableViewCell *cell = [tableViewController.tableView dequeueReusableCellWithIdentifier:NSStringFromClass([PostTableViewCell class])]; Post *testPost = [[Post alloc]init]; [testPost setTimestamp:[NSDate dateWithTimeIntervalSince1970:1420120800]]; [testPost setAuthor:[[User alloc] init]]; testPost.author.displayName = @"Test User"; testPost.text = @"Jujubes pie wafer (…)“; CGFloat heightForCell = [PostTableViewCell heightForPostText:testPost.text cellWidth:320]; [cell setFrame:CGRectMake(0, 0, 320, heightForCell)]; PostViewModel *viewModel = [[PostViewModel alloc]initWithPost:testPost]; cell.authorNameLabel.text = viewModel.authorName; cell.authorAvatarImageView.image = [UIImage imageNamed:@"no_avatar"] ; cell.postTextView.attributedText = viewModel.text; cell.postDateLabel.text = viewModel.dateString; FBSnapshotVerifyView(cell, nil); }
  8. - (void)testWithViewModel { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];

    ThreadTableViewController *tableViewController = [storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([ThreadTableVie wController class])]; PostTableViewCell *cell = [tableViewController.tableView dequeueReusableCellWithIdentifier:NSStringFromClass([PostTableViewCell class])]; Post *testPost = [[Post alloc]init]; [testPost setTimestamp:[NSDate dateWithTimeIntervalSince1970:1420120800]]; [testPost setAuthor:[[User alloc] init]]; testPost.author.displayName = @"Test User"; testPost.text = @"Jujubes pie wafer (…)“; CGFloat heightForCell = [PostTableViewCell heightForPostText:testPost.text cellWidth:320]; [cell setFrame:CGRectMake(0, 0, 320, heightForCell)]; PostViewModel *viewModel = [[PostViewModel alloc]initWithPost:testPost]; cell.authorNameLabel.text = viewModel.authorName; cell.authorAvatarImageView.image = [UIImage imageNamed:@"no_avatar"] ; cell.postTextView.attributedText = viewModel.text; cell.postDateLabel.text = viewModel.dateString; FBSnapshotVerifyView(cell, nil); }
  9. - (void)testWithViewModel { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];

    ThreadTableViewController *tableViewController = [storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([ThreadTableVie wController class])]; PostTableViewCell *cell = [tableViewController.tableView dequeueReusableCellWithIdentifier:NSStringFromClass([PostTableViewCell class])]; Post *testPost = [[Post alloc]init]; [testPost setTimestamp:[NSDate dateWithTimeIntervalSince1970:1420120800]]; [testPost setAuthor:[[User alloc] init]]; testPost.author.displayName = @"Test User"; testPost.text = @"Jujubes pie wafer (…)“; CGFloat heightForCell = [PostTableViewCell heightForPostText:testPost.text cellWidth:320]; [cell setFrame:CGRectMake(0, 0, 320, heightForCell)]; PostViewModel *viewModel = [[PostViewModel alloc]initWithPost:testPost]; cell.authorNameLabel.text = viewModel.authorName; cell.authorAvatarImageView.image = [UIImage imageNamed:@"no_avatar"] ; cell.postTextView.attributedText = viewModel.text; cell.postDateLabel.text = viewModel.dateString; FBSnapshotVerifyView(cell, nil); }
  10. - (void)testWithViewModel { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];

    ThreadTableViewController *tableViewController = [storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([ThreadTableVie wController class])]; PostTableViewCell *cell = [tableViewController.tableView dequeueReusableCellWithIdentifier:NSStringFromClass([PostTableViewCell class])]; Post *testPost = [[Post alloc]init]; [testPost setTimestamp:[NSDate dateWithTimeIntervalSince1970:1420120800]]; [testPost setAuthor:[[User alloc] init]]; testPost.author.displayName = @"Test User"; testPost.text = @"Jujubes pie wafer (…)“; CGFloat heightForCell = [PostTableViewCell heightForPostText:testPost.text cellWidth:320]; [cell setFrame:CGRectMake(0, 0, 320, heightForCell)]; PostViewModel *viewModel = [[PostViewModel alloc]initWithPost:testPost]; cell.authorNameLabel.text = viewModel.authorName; cell.authorAvatarImageView.image = [UIImage imageNamed:@"no_avatar"] ; cell.postTextView.attributedText = viewModel.text; cell.postDateLabel.text = viewModel.dateString; FBSnapshotVerifyView(cell, nil); }
  11. - (void)testWithViewModel { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];

    ThreadTableViewController *tableViewController = [storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([ThreadTableVie wController class])]; PostTableViewCell *cell = [tableViewController.tableView dequeueReusableCellWithIdentifier:NSStringFromClass([PostTableViewCell class])]; Post *testPost = [[Post alloc]init]; [testPost setTimestamp:[NSDate dateWithTimeIntervalSince1970:1420120800]]; [testPost setAuthor:[[User alloc] init]]; testPost.author.displayName = @"Test User"; testPost.text = @"Jujubes pie wafer (…)“; CGFloat heightForCell = [PostTableViewCell heightForPostText:testPost.text cellWidth:320]; [cell setFrame:CGRectMake(0, 0, 320, heightForCell)]; PostViewModel *viewModel = [[PostViewModel alloc]initWithPost:testPost]; cell.authorNameLabel.text = viewModel.authorName; cell.authorAvatarImageView.image = [UIImage imageNamed:@"no_avatar"] ; cell.postTextView.attributedText = viewModel.text; cell.postDateLabel.text = viewModel.dateString; FBSnapshotVerifyView(cell, nil); }
  12. Test Driven? • Don’t use your design mockups as references!

    • Finding resizing issues is easy • Testing edge cases is easy • Helps a lot during development
  13. Wins • Fast tests for UI components (100ms per test)

    • Test multiple sizes and landscape without juggling devices • Encourages to clearly separate views and logic (MVVM) • Good at finding regression problems in complex apps
  14. Problems • Reference images need to be taken for each

    simulator once • False positives are possible (but not common) • Can not replace UI tests completely but makes the number of tests smaller