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.

Ben Scheirman

October 10, 2013
Tweet

More Decks by Ben Scheirman

Other Decks in Programming

Transcript

  1. Automated Testing
    with Objective-C
    Ben Scheirman

    View Slide

  2. Ben Scheirman
    Director of Development at ChaiOne
    Houston,

    View Slide

  3. View Slide

  4. nsscreencast.com

    View Slide

  5. Automated Tests

    View Slide

  6. Automated Tests

    View Slide

  7. Does your
    application work?

    View Slide

  8. ˒ˑˑˑˑ

    View Slide

  9. Application
    Code
    Automated Tests

    View Slide

  10. Application
    Code
    Automated Tests

    View Slide

  11. Application
    Code
    Automated Tests




    x
    x

    View Slide

  12. Unit Tests
    Integration Tests
    Acceptance Tests

    View Slide

  13. Unit Tests

    View Slide

  14. Unit Tests

    View Slide

  15. Integration Tests

    View Slide

  16. Acceptance Tests

    View Slide

  17. Acceptance Tests

    View Slide

  18. View Slide

  19. View Slide

  20. What's in a test run?

    View Slide

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

    View Slide

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

    View Slide

  23. What's in a test?
    Test

    View Slide

  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 :(");
    }

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  28. 1 assertion
    per
    test

    View Slide

  29. View Slide

  30. Unit Tests come default on
    Xcode 5

    View Slide

  31. View Slide

  32. ⌘U

    View Slide

  33. View Slide

  34. Demo: Guess the
    number game

    View Slide

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

    View Slide

  36. Game

    View Slide

  37. Game
    RNG

    View Slide

  38. Game RNG

    View Slide

  39. Game
    RNG

    View Slide

  40. Game
    RNG
    NG
    Stub

    View Slide

  41. Demo: Stubbing with
    OCMock

    View Slide

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

    View Slide

  43. DETOUR AHEAD
    Behavior Driven Development

    View Slide

  44. GIVEN WHEN THEN

    View Slide

  45. GIVEN WHEN THEN
    Some Behavior

    View Slide

  46. GIVEN WHEN THEN
    Some Behavior I do X

    View Slide

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

    View Slide

  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

    View Slide

  49. Kiwi
    expect(sum).to.equal(12);
    [theValue(sum) should] equal:theValue(12)];
    Expecta

    View Slide

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

    View Slide

  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

    View Slide

  52. UIButton *button = _vc.loginButton;
    NSArray *actions = [button
    actionsForTarget:_vc
    forControlEvent:UIControlEventTouchUpInside
    ];
    expect(actions[0]).to.equal(@"loginTapped:");
    Testing Wired Actions

    View Slide

  53. Testing Block Based
    interaction
    VC
    Login
    Service
    External
    API

    View Slide

  54. Testing Block Based
    interaction
    VC
    Login
    Service
    External
    API

    View Slide

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

    View Slide

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

    View Slide

  57. VC
    Login
    Service
    External
    API
    ^{ }

    View Slide

  58. VC
    Login
    Service
    External
    API
    ^{ }
    System

    View Slide

  59. VC
    Login
    Service
    External
    API
    ^{ }
    System

    View Slide

  60. Step 1: Verify Login
    Service is called

    View Slide

  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];
    });
    });

    View Slide

  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];
    });
    });

    View Slide

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

    View Slide

  64. Step 2: Define the
    behavior when the login
    service calls back
    2 cases: success & failure

    View Slide

  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];
    });
    });

    View Slide

  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];
    });
    });

    View Slide

  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
    }]];

    View Slide

  68. Where to go from here?
    Explore Acceptance Test Frameworks:
    KIF
    Frank
    UIAutomation

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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!

    View Slide

  74. Thank you!
    Ben Scheirman
    @subdigital
    benscheirman.com

    View Slide