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

Automated Testing with Objective-C

Automated Testing with Objective-C

A quick tour of automated testing in Objective-C, starting with why we should care. Covers the basics of testing with XCTest, when to leverage mocks for testing interactions, how to test Storyboard / IB related things like instantiated view controllers or wired up actions. Follows it up with a more complex example of testing a callback block.

Originally presented at Mobile Developer Summit, Oct 10th in Bangalore, India.

023a6a37e8177cb2f84a236bbce643cf?s=128

Ben Scheirman

October 10, 2013
Tweet

Transcript

  1. Automated Testing with Objective-C Ben Scheirman

  2. Ben Scheirman Director of Development at ChaiOne Houston,

  3. None
  4. nsscreencast.com

  5. Automated Tests

  6. Automated Tests

  7. Does your application work?

  8. ˒ˑˑˑˑ

  9. Application Code Automated Tests

  10. Application Code Automated Tests

  11. Application Code Automated Tests ✓ ✓ ✓ ✓ x x

  12. Unit Tests Integration Tests Acceptance Tests

  13. Unit Tests

  14. Unit Tests

  15. Integration Tests

  16. Acceptance Tests

  17. Acceptance Tests

  18. None
  19. None
  20. What's in a test run?

  21. What's in a test run? Setup Test Test Tear Down

    Setup Tear Down
  22. What's in a test? Test Arrange Act Assert

  23. What's in a test? Test

  24. What's in a test? Test - (void)test_adding_numbers { Calculator *calc

    = [Calculator new]; int sum = [calc add:x to:y]; XCTAssertEquals(7, sum, @"math is hard :("); }
  25. What's in a test? Test - (void)test_adding_numbers { Calculator *calc

    = [Calculator new]; int sum = [calc add:x to:y]; XCTAssertEquals(7, sum, @"math is hard :("); } Arrange
  26. What's in a test? Test - (void)test_adding_numbers { Calculator *calc

    = [Calculator new]; int sum = [calc add:x to:y]; XCTAssertEquals(7, sum, @"math is hard :("); } Act
  27. What's in a test? Test - (void)test_adding_numbers { Calculator *calc

    = [Calculator new]; int sum = [calc add:x to:y]; XCTAssertEquals(7, sum, @"math is hard :("); } Assert
  28. 1 assertion per test

  29. None
  30. Unit Tests come default on Xcode 5

  31. None
  32. ⌘U

  33. None
  34. Demo: Guess the number game

  35. How do we know what number it will generate?

  36. Game

  37. Game RNG

  38. Game RNG

  39. Game RNG

  40. Game RNG NG Stub

  41. Demo: Stubbing with OCMock

  42. What about... View Controllers? Interface Builder? Storyboards?

  43. DETOUR AHEAD Behavior Driven Development

  44. GIVEN WHEN THEN

  45. GIVEN WHEN THEN Some Behavior

  46. GIVEN WHEN THEN Some Behavior I do X

  47. GIVEN WHEN THEN Some Behavior I do X Y should

    happen
  48. SpecBegin(CalculatorSpec) describe(@"Calculator", ^{ __block Calculator *_calc; beforeEach(^{ _calc = [Calculator

    new]; }); it(@"should add two numbers", ^{ int sum = [_calc add:5 to:7]; expect(sum).to.equal(12); }); }); SpecEnd Specta / Expecta
  49. Kiwi expect(sum).to.equal(12); [theValue(sum) should] equal:theValue(12)]; Expecta

  50. Kiwi Leverage OCMock for mocks & stubs Built-in powerful mocking

    & stubbing functionality Expecta
  51. UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil]; UINavigationController *nav = [mainStoryboard

    instantiateInitialViewController]; ViewController *vc = (ViewController *)[nav visibleViewController]; UIView *view = vc.view; expect(view).toNot.beNil(); Testing Storyboards
  52. UIButton *button = _vc.loginButton; NSArray *actions = [button actionsForTarget:_vc forControlEvent:UIControlEventTouchUpInside

    ]; expect(actions[0]).to.equal(@"loginTapped:"); Testing Wired Actions
  53. Testing Block Based interaction VC Login Service External API

  54. Testing Block Based interaction VC Login Service External API

  55. Testing Block Based interaction VC Login Service External API ^{

    } ^(User *user){ if (user) { NSLog(@"Logged in!"); } else { NSLog(@"Invalid login..."); } }
  56. Testing Block Based interaction VC Login Service External API ^{

    } ^(User *user){ if (user) { NSLog(@"Logged in!"); } else { NSLog(@"Invalid login..."); } }
  57. VC Login Service External API ^{ }

  58. VC Login Service External API ^{ } System

  59. VC Login Service External API ^{ } System

  60. Step 1: Verify Login Service is called

  61. describe(@"logging in", ^{ it(@"should verify username & password w/ login

    svc", ^{ id mockLoginService = [OCMockObject mockForClass:[LoginService class]]; [[mockLoginService expect] verifyUsername:@"user1" password:@"password1" completion: [OCMArg any]]; _vc.loginService = mockLoginService; _vc.usernameTextField.text = @"user1"; _vc.passwordTextField.text = @"password1"; [_vc loginTapped:nil]; [mockLoginService verify]; }); });
  62. describe(@"logging in", ^{ it(@"should verify username & password w/ login

    svc", ^{ id mockLoginService = [OCMockObject mockForClass:[LoginService class]]; [[mockLoginService expect] verifyUsername:@"user1" password:@"password1" completion: [OCMArg any]]; _vc.loginService = mockLoginService; _vc.usernameTextField.text = @"user1"; _vc.passwordTextField.text = @"password1"; [_vc loginTapped:nil]; [mockLoginService verify]; }); });
  63. Step 2: Define the behavior when the login service calls

    back
  64. Step 2: Define the behavior when the login service calls

    back 2 cases: success & failure
  65. describe(@"logging in", ^{ it(@"should welcome the user when the login

    is valid", ^{ id mockLoginService = [OCMockObject mockForClass:[LoginService class]]; [[mockLoginService expect] verifyUsername:[OCMArg any] password:[OCMArg any] completion:... ]; _vc.loginService = mockLoginService; [_vc loginTapped:nil]; [mockLoginService verify]; }); });
  66. describe(@"logging in", ^{ it(@"should welcome the user when the login

    is valid", ^{ id mockLoginService = [OCMockObject mockForClass:[LoginService class]]; [[mockLoginService expect] verifyUsername:[OCMArg any] password:[OCMArg any] completion:... ]; _vc.loginService = mockLoginService; [_vc loginTapped:nil]; [mockLoginService verify]; }); });
  67. [[mockLoginService expect] verifyUsername:[OCMArg any] password:[OCMArg any] completion:[OCMArg checkWithBlock:^BOOL(id completionBlock) {

    // fake login successful for a user id mockUser = [OCMockObject mockForClass:[User class]]; completionBlock(mockUser); return YES; // signify that this argument is valid }]];
  68. Where to go from here? Explore Acceptance Test Frameworks: KIF

    Frank UIAutomation
  69. Some Parting Tips... If it's hard to test, improve your

    design
  70. Some Parting Tips... If it's hard to test, improve your

    design Try to test 1 thing
  71. Some Parting Tips... If it's hard to test, improve your

    design Try to test 1 thing Write tests first to avoid creating untestable designs
  72. Some Parting Tips... If it's hard to test, improve your

    design Try to test 1 thing Write tests first to avoid creating untestable designs Use a continuous integration server
  73. Some Parting Tips... If it's hard to test, improve your

    design Try to test 1 thing Write tests first to avoid creating untestable designs Use a continuous integration server PRACTICE!
  74. Thank you! Ben Scheirman @subdigital benscheirman.com