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

BDD on iOS with Kiwi

Ian Murray
February 27, 2014

BDD on iOS with Kiwi

Ian Murray

February 27, 2014

Transcript

69 slides
  1. Why? • You make sure you don’t break anything in

    the future • You can optimize/refactor code knowing it won’t break
  2. How To Make It Worth The Effort • Don’t write

    tests for trivial things - (NSInteger)add:(NSInteger)this to:(NSInteger)that { return this + that; }
  3. How To Make It Worth The Effort • Don’t write

    tests for trivial things • If you find a bug, write some tests to reproduce it and then fix it
  4. How To Make It Worth The Effort • Don’t write

    tests for trivial things • If you find a bug, write some tests to reproduce it and then fix it • Don’t try and test everything, (especially UI)
  5. Installing $ [sudo] gem install cocoapods $ pod setup $

    cd path/to/project $ pod init $ vim Podfile
  6. Installing $ [sudo] gem install cocoapods $ pod setup $

    cd path/to/project $ pod init $ vim Podfile target "BDDKiwi" do ! end ! target "BDDKiwiTests" do pod 'Kiwi/XCTest' # Xcode 5 end
  7. Installing $ [sudo] gem install cocoapods $ pod setup $

    cd path/to/project $ pod init $ vim Podfile target "BDDKiwi" do ! end ! target "BDDKiwiTests" do pod 'Kiwi/XCTest' # Xcode 5 end $ pod install $ open project.xcworkspace
  8. Our First Spec SPEC_BEGIN(CalculatorSpec) ! describe(@"Calculator", ^{ describe(@"adding up two

    numbers", ^{ it(@"should add two numbers", ^{ [[theValue([Calculator add:1 to:1]) should] equal:theValue(2)]; }); }); }); ! ! SPEC_END
  9. Our First Spec SPEC_BEGIN(CalculatorSpec) ! describe(@"Calculator", ^{ describe(@"adding up two

    numbers", ^{ it(@"should add two numbers", ^{ [[theValue([Calculator add:1 to:1]) should] equal:theValue(2)]; }); }); }); ! ! SPEC_END
  10. Our First Spec SPEC_BEGIN(CalculatorSpec) ! describe(@"Calculator", ^{ describe(@"adding up two

    numbers", ^{ it(@"should add two numbers", ^{ [[theValue([Calculator add:1 to:1]) should] equal:theValue(2)]; }); }); }); ! ! SPEC_END
  11. Our First Spec SPEC_BEGIN(CalculatorSpec) ! describe(@"Calculator", ^{ describe(@"adding up two

    numbers", ^{ it(@"should add two numbers", ^{ [[theValue([Calculator add:1 to:1]) should] equal:theValue(2)]; }); }); }); ! ! SPEC_END
  12. Our First Spec SPEC_BEGIN(CalculatorSpec) ! describe(@"Calculator", ^{ describe(@"adding up two

    numbers", ^{ it(@"should add two numbers", ^{ [[theValue([Calculator add:1 to:1]) should] equal:theValue(2)]; }); }); }); ! ! SPEC_END
  13. Our First Spec SPEC_BEGIN(CalculatorSpec) ! describe(@"Calculator", ^{ describe(@"adding up two

    numbers", ^{ it(@"should add two numbers", ^{ [[theValue([Calculator add:1 to:1]) should] equal:theValue(2)]; }); }); }); ! ! SPEC_END
  14. More Specs describe(@"adding up to NSNumbers", ^{ it(@"should add up

    to NSNumbers", ^{ [[[Calculator addNumber:@1 toNumber:@1] should] equal:@2]; }); });
  15. More Specs describe(@"adding up to NSNumbers", ^{ it(@"should add up

    to NSNumbers", ^{ [[[Calculator addNumber:@1 toNumber:@1] should] equal:@2]; }); });
  16. More Specs describe(@"adding up to NSNumbers", ^{ it(@"should add up

    to NSNumbers", ^{ [[[Calculator addNumber:@1 toNumber:@1] should] equal:@2]; }); });
  17. More Specs describe(@"adding up to NSNumbers", ^{ it(@"should add up

    to NSNumbers", ^{ [[[Calculator addNumber:@1 toNumber:@1] should] equal:@2]; }); });
  18. More Specs describe(@"adding up to NSNumbers", ^{ it(@"should add up

    to NSNumbers", ^{ [[[Calculator addNumber:@1 toNumber:@1] should] equal:@2]; }); });
  19. Async Specs describe(@"asynchronously adding NSNumbers", ^{ it(@"should add up two

    numbers asynchronously", ^{ __block NSNumber *result; [Calculator addNumber:@1 toNumber:@1 completionHandler:^(NSNumber *_) { result = _; }]; [[expectFutureValue(result) shouldEventually] equal:@2]; }); });
  20. Async Specs describe(@"asynchronously adding NSNumbers", ^{ it(@"should add up two

    numbers asynchronously", ^{ __block NSNumber *result; [Calculator addNumber:@1 toNumber:@1 completionHandler:^(NSNumber *_) { result = _; }]; [[expectFutureValue(result) shouldEventually] equal:@2]; }); });
  21. Async Specs describe(@"asynchronously adding NSNumbers", ^{ it(@"should add up two

    numbers asynchronously", ^{ __block NSNumber *result; [Calculator addNumber:@1 toNumber:@1 completionHandler:^(NSNumber *_) { result = _; }]; [[expectFutureValue(result) shouldEventually] equal:@2]; }); });
  22. Async Specs describe(@"asynchronously adding NSNumbers", ^{ it(@"should add up two

    numbers asynchronously", ^{ __block NSNumber *result; [Calculator addNumber:@1 toNumber:@1 completionHandler:^(NSNumber *_) { result = _; }]; [[expectFutureValue(result) shouldEventually] equal:@2]; }); });
  23. Async Specs describe(@"asynchronously adding NSNumbers", ^{ it(@"should add up two

    numbers asynchronously", ^{ __block NSNumber *result; [Calculator addNumber:@1 toNumber:@1 completionHandler:^(NSNumber *_) { result = _; }]; [[expectFutureValue(result) shouldEventuallyBeforeTimingOutAfter(10)] equal:@2]; }); }); What if our calculation takes way longer?
  24. Async Specs describe(@"asynchronously adding NSNumbers", ^{ it(@"should add up two

    numbers asynchronously", ^{ __block NSNumber *result; [Calculator addNumber:@1 toNumber:@1 completionHandler:^(NSNumber *_) { result = _; }]; [[expectFutureValue(result) shouldEventuallyBeforeTimingOutAfter(10)] equal:@2]; }); }); What if our calculation takes way longer?
  25. Async Specs describe(@"asynchronously adding NSNumbers", ^{ it(@"should add up two

    numbers asynchronously", ^{ __block NSNumber *result; [Calculator addNumber:@1 toNumber:@1 completionHandler:^(NSNumber *_) { result = _; }]; [[expectFutureValue(result) shouldEventuallyBeforeTimingOutAfter(10)] equal:@2]; }); }); What if our calculation takes way longer? Unfortunately, not an exact science.
  26. Mocks & Stubs • The Unit in Unit Testing is

    important. • You want to test units of code and not your entire app (those would be integration tests).
  27. Mocks & Stubs • The Unit in Unit Testing is

    important. • You want to test units of code and not your entire app (those would be integration tests). • You don’t want your tests to depend on network connectivity • Server responses should remain the same, and server should have its own Unit Tests Suite.
  28. Mocks & Stubs • The Unit in Unit Testing is

    important. • You want to test units of code and not your entire app (those would be integration tests). • You don’t want your tests to depend on network connectivity • Server responses should remain the same, and server should have its own Unit Tests Suite. • You want tests to be fast
  29. Mocks • Mock objects are “fake” objects that you create

    on-the-fly • They do what you configure them to do
  30. Mocks • Mock objects are “fake” objects that you create

    on-the-fly • They do what you configure them to do • Not always useful and normally not necessary
  31. Stubs • Stubbing is used to switch the functionality of

    a method for another • This way you can have an object return what you want, effectively replacing it
  32. Stubs • Stubbing is used to switch the functionality of

    a method for another • This way you can have an object return what you want, effectively replacing it • Useful to set conditions for a given test case
  33. Stubs • Stubbing is used to switch the functionality of

    a method for another • This way you can have an object return what you want, effectively replacing it • Useful to set conditions for a given test case • You can avoid depending on other methods
  34. typedef NS_ENUM(NSUInteger, CruiserCoreStatus) { CruiserCoreStatusNormal, CruiserCoreStatusAlert, CruiserCoreStatusCritical }; ! //////////////////////////////////////////////////////////////////////

    ! @interface Cruiser : NSObject ! @property (nonatomic,assign) NSUInteger speed; ! - (NSUInteger)energyLevelForCurrentSpeed; ! /** If energyLevel < 5, then returns CruiserCoreStatusNormal. If 5 <= energyLevel < 10 , then returns CruiserCoreStatusAlert. If 10 <= energyLevel , then returns CruiserCoreStatusCritical. */ - (CruiserCoreStatus)coreStatus; ! @end
  35. describe(@"coreStatus", ^{ it(@"should return normal for low energy levels", ^{

    Cruiser *cruiser = [Cruiser new]; [[cruiser stubAndReturn:theValue(3)] energyLevelForCurrentSpeed]; [[theValue([cruiser coreStatus]) should] equal:theValue(CruiserCoreStatusNormal)]; }); it(@"should return alert for medium energy levels", ^{ Cruiser *cruiser = [Cruiser new]; [cruiser stub:@selector(energyLevelForCurrentSpeed) andReturn:theValue(7)]; [[theValue([cruiser coreStatus]) should] equal:theValue(CruiserCoreStatusAlert)]; }); it(@"should return critical for high energy levels", ^{ Cruiser *cruiser = [Cruiser new]; [cruiser stub:@selector(energyLevelForCurrentSpeed) withBlock:^id(NSArray *params) { return theValue(15); }]; [[theValue([cruiser coreStatus]) should] equal:theValue(CruiserCoreStatusCritical)]; }); });
  36. describe(@"coreStatus", ^{ it(@"should return normal for low energy levels", ^{

    Cruiser *cruiser = [Cruiser new]; [[cruiser stubAndReturn:theValue(3)] energyLevelForCurrentSpeed]; [[theValue([cruiser coreStatus]) should] equal:theValue(CruiserCoreStatusNormal)]; }); it(@"should return alert for medium energy levels", ^{ Cruiser *cruiser = [Cruiser new]; [cruiser stub:@selector(energyLevelForCurrentSpeed) andReturn:theValue(7)]; [[theValue([cruiser coreStatus]) should] equal:theValue(CruiserCoreStatusAlert)]; }); it(@"should return critical for high energy levels", ^{ Cruiser *cruiser = [Cruiser new]; [cruiser stub:@selector(energyLevelForCurrentSpeed) withBlock:^id(NSArray *params) { return theValue(15); }]; [[theValue([cruiser coreStatus]) should] equal:theValue(CruiserCoreStatusCritical)]; }); });
  37. describe(@"coreStatus", ^{ it(@"should return normal for low energy levels", ^{

    Cruiser *cruiser = [Cruiser new]; [[cruiser stubAndReturn:theValue(3)] energyLevelForCurrentSpeed]; [[theValue([cruiser coreStatus]) should] equal:theValue(CruiserCoreStatusNormal)]; }); it(@"should return alert for medium energy levels", ^{ Cruiser *cruiser = [Cruiser new]; [cruiser stub:@selector(energyLevelForCurrentSpeed) andReturn:theValue(7)]; [[theValue([cruiser coreStatus]) should] equal:theValue(CruiserCoreStatusAlert)]; }); it(@"should return critical for high energy levels", ^{ Cruiser *cruiser = [Cruiser new]; [cruiser stub:@selector(energyLevelForCurrentSpeed) withBlock:^id(NSArray *params) { return theValue(15); }]; [[theValue([cruiser coreStatus]) should] equal:theValue(CruiserCoreStatusCritical)]; }); });
  38. describe(@"coreStatus", ^{ it(@"should return normal for low energy levels", ^{

    Cruiser *cruiser = [Cruiser new]; [[cruiser stubAndReturn:theValue(3)] energyLevelForCurrentSpeed]; [[theValue([cruiser coreStatus]) should] equal:theValue(CruiserCoreStatusNormal)]; }); it(@"should return alert for medium energy levels", ^{ Cruiser *cruiser = [Cruiser new]; [cruiser stub:@selector(energyLevelForCurrentSpeed) andReturn:theValue(7)]; [[theValue([cruiser coreStatus]) should] equal:theValue(CruiserCoreStatusAlert)]; }); it(@"should return critical for high energy levels", ^{ Cruiser *cruiser = [Cruiser new]; [cruiser stub:@selector(energyLevelForCurrentSpeed) withBlock:^id(NSArray *params) { return theValue(15); }]; [[theValue([cruiser coreStatus]) should] equal:theValue(CruiserCoreStatusCritical)]; }); });
  39. - (void)getLatestResourcesWithCompletionHandler:(void (^)(NSArray *resources))completion { AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

    [manager GET:@"http://example.com/resources.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { NSMutableArray *resources = [NSMutableArray array]; // Parse the response for (NSDictionary *resource in responseObject) { Resource *r = [Resource new]; r.name = resource[@"name"]; r.identifier = resource[@"id"]; [resources addObject:r]; } if (completion) completion(resources.copy); } failure:nil]; }
  40. it(@"should return an array of resources", ^{ NSArray *mockJSON =

    @[ @{ @"id": @123, @"name": @"John Doe" }, @{ @"id": @124, @"name": @"Jane Doe" }, ]; // Stub AFNetworking AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; [AFHTTPRequestOperationManager stub:@selector(manager) andReturn:manager]; [[AFHTTPRequestOperationManager manager] stub:@selector(GET:parameters:success:failure:) withBlock:^id(NSArray *params) { // Get the success handler void (^success)(id _, id responseObject) = params[2]; // Call it with our JSON success(nil, mockJSON); return nil; // We don't care of what's returned }]; __block NSArray *response = nil; [Network getLatestResourcesWithCompletionHandler:^(NSArray *_) { response = _; }]; [[expectFutureValue([response.firstObject identifier]) shouldEventuallyBeforeTimingOutAfter(10)] equal:@123]; [[expectFutureValue([response.firstObject name]) shouldEventuallyBeforeTimingOutAfter(10)] equal:@"John Doe"]; });
  41. it(@"should return an array of resources", ^{ NSArray *mockJSON =

    @[ @{ @"id": @123, @"name": @"John Doe" }, @{ @"id": @124, @"name": @"Jane Doe" }, ]; // Stub AFNetworking AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; [AFHTTPRequestOperationManager stub:@selector(manager) andReturn:manager]; [[AFHTTPRequestOperationManager manager] stub:@selector(GET:parameters:success:failure:) withBlock:^id(NSArray *params) { // Get the success handler void (^success)(id _, id responseObject) = params[2]; // Call it with our JSON success(nil, mockJSON); return nil; // We don't care of what's returned }]; __block NSArray *response = nil; [Network getLatestResourcesWithCompletionHandler:^(NSArray *_) { response = _; }]; [[expectFutureValue([response.firstObject identifier]) shouldEventuallyBeforeTimingOutAfter(10)] equal:@123]; [[expectFutureValue([response.firstObject name]) shouldEventuallyBeforeTimingOutAfter(10)] equal:@"John Doe"]; });
  42. it(@"should return an array of resources", ^{ NSArray *mockJSON =

    @[ @{ @"id": @123, @"name": @"John Doe" }, @{ @"id": @124, @"name": @"Jane Doe" }, ]; // Stub AFNetworking AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; [AFHTTPRequestOperationManager stub:@selector(manager) andReturn:manager]; [[AFHTTPRequestOperationManager manager] stub:@selector(GET:parameters:success:failure:) withBlock:^id(NSArray *params) { // Get the success handler void (^success)(id _, id responseObject) = params[2]; // Call it with our JSON success(nil, mockJSON); return nil; // We don't care of what's returned }]; __block NSArray *response = nil; [Network getLatestResourcesWithCompletionHandler:^(NSArray *_) { response = _; }]; [[expectFutureValue([response.firstObject identifier]) shouldEventuallyBeforeTimingOutAfter(10)] equal:@123]; [[expectFutureValue([response.firstObject name]) shouldEventuallyBeforeTimingOutAfter(10)] equal:@"John Doe"]; });
  43. it(@"should return an array of resources", ^{ NSArray *mockJSON =

    @[ @{ @"id": @123, @"name": @"John Doe" }, @{ @"id": @124, @"name": @"Jane Doe" }, ]; // Stub AFNetworking AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; [AFHTTPRequestOperationManager stub:@selector(manager) andReturn:manager]; [[AFHTTPRequestOperationManager manager] stub:@selector(GET:parameters:success:failure:) withBlock:^id(NSArray *params) { // Get the success handler void (^success)(id _, id responseObject) = params[2]; // Call it with our JSON success(nil, mockJSON); return nil; // We don't care of what's returned }]; __block NSArray *response = nil; [Network getLatestResourcesWithCompletionHandler:^(NSArray *_) { response = _; }]; [[expectFutureValue([response.firstObject identifier]) shouldEventuallyBeforeTimingOutAfter(10)] equal:@123]; [[expectFutureValue([response.firstObject name]) shouldEventuallyBeforeTimingOutAfter(10)] equal:@"John Doe"]; });
  44. it(@"should return an array of resources", ^{ NSArray *mockJSON =

    @[ @{ @"id": @123, @"name": @"John Doe" }, @{ @"id": @124, @"name": @"Jane Doe" }, ]; // Stub AFNetworking AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; [AFHTTPRequestOperationManager stub:@selector(manager) andReturn:manager]; [[AFHTTPRequestOperationManager manager] stub:@selector(GET:parameters:success:failure:) withBlock:^id(NSArray *params) { // Get the success handler void (^success)(id _, id responseObject) = params[2]; // Call it with our JSON success(nil, mockJSON); return nil; // We don't care of what's returned }]; __block NSArray *response = nil; [Network getLatestResourcesWithCompletionHandler:^(NSArray *_) { response = _; }]; [[expectFutureValue([response.firstObject identifier]) shouldEventuallyBeforeTimingOutAfter(10)] equal:@123]; [[expectFutureValue([response.firstObject name]) shouldEventuallyBeforeTimingOutAfter(10)] equal:@"John Doe"]; });

More Decks by Ian Murray

Other Decks in Programming