SlideShare uma empresa Scribd logo
1 de 71
Baixar para ler offline
iOS Behavior-Driven
Development
Testing RESTful Applications with Kiwi and Nocilla
March 9th, 2014
Brian Gesiak
Research Student, The University of Tokyo
@modocache #startup_ios
Today
• Behavior-driven development (BDD)
• iOS behavior-driven development
• Kiwi
• Testing asynchronous networking
• Nocilla
Test-Driving Network Code
Motivation
• Let’s say we want to display a user’s

repositories on GitHub
• We can GET JSON from the GitHub
API
• https://api.github.com/users/
{{ username }}/repos.json
Test-Driving Network Code
Motivation

/// GET /users/:username/repos
!

[
{
"id": 1296269,
"name": "Hello-World",
"description": "My first repo!",
/* ... */
}
]
Test-Driving Network Code
Demonstration
Building the App
Behavior-Driven Development Using Kiwi
• Behavior-driven development (BDD) is an extension of

test-driven development
Test-Driven Development
Test-Driven Development
• Red: Write a test and watch it fail
Test-Driven Development
• Red: Write a test and watch it fail
• Green: Pass the test (by writing as little code as possible)
Test-Driven Development
• Red: Write a test and watch it fail
• Green: Pass the test (by writing as little code as possible)
• Refactor: Remove duplication
Test-Driven Development
• Red: Write a test and watch it fail
• Green: Pass the test (by writing as little code as possible)
• Refactor: Remove duplication
• Repeat
Example of iOS TDD Using XCTest
Example of iOS TDD Using XCTest
// Create an XCTestCase subclass
@interface GHVRepoCollectionTests : XCTestCase
// The subject of our tests
@property (nonatomic, strong) GHVCollection *collection;
@end
!
@implementation GHVRepoCollectionTests
// Set up the `repos` collection for each test
- (void)setUp {
[super setUp];
self.collection = [GHVCollection new];
}
// Add a test
- (void)testAddingARepo {
// Add a repo
[self.collection addRepo:[GHVRepo new]];
!
// Assert the number of repos is now one
XCTAssertEqual([self.collection.repos count], 1,
@"Expected one repository");
}
@end
Example of iOS TDD Using XCTest
// Create an XCTestCase subclass
@interface GHVRepoCollectionTests : XCTestCase
// The subject of our tests
@property (nonatomic, strong) GHVCollection *collection;
@end
!
@implementation GHVRepoCollectionTests
// Set up the `repos` collection for each test
- (void)setUp {
[super setUp];
self.collection = [GHVCollection new];
}
// Add a test
- (void)testAddingARepo {
// Add a repo
[self.collection addRepo:[GHVRepo new]];
!
// Assert the number of repos is now one
XCTAssertEqual([self.collection.repos count], 1,
@"Expected one repository");
}
@end
Example of iOS TDD Using XCTest
// Create an XCTestCase subclass
@interface GHVRepoCollectionTests : XCTestCase
// The subject of our tests
@property (nonatomic, strong) GHVCollection *collection;
@end
!
@implementation GHVRepoCollectionTests
// Set up the `repos` collection for each test
- (void)setUp {
[super setUp];
self.collection = [GHVCollection new];
}
// Add a test
- (void)testAddingARepo {
// Add a repo
[self.collection addRepo:[GHVRepo new]];
!
// Assert the number of repos is now one
XCTAssertEqual([self.collection.repos count], 1,
@"Expected one repository");
}
@end
Example of iOS TDD Using XCTest
// Create an XCTestCase subclass
@interface GHVRepoCollectionTests : XCTestCase
// The subject of our tests
@property (nonatomic, strong) GHVCollection *collection;
@end
!
@implementation GHVRepoCollectionTests
// Set up the `repos` collection for each test
- (void)setUp {
[super setUp];
self.collection = [GHVCollection new];
}
// Add a test
- (void)testAddingARepo {
// Add a repo
[self.collection addRepo:[GHVRepo new]];
!
// Assert the number of repos is now one
XCTAssertEqual([self.collection.repos count], 1,
@"Expected one repository");
}
@end
Example of iOS TDD Using XCTest
// Create an XCTestCase subclass
@interface GHVRepoCollectionTests : XCTestCase
// The subject of our tests
@property (nonatomic, strong) GHVCollection *collection;
@end
!
@implementation GHVRepoCollectionTests
// Set up the `repos` collection for each test
- (void)setUp {
[super setUp];
self.collection = [GHVCollection new];
}
// Add a test
- (void)testAddingARepo {
// Add a repo
[self.collection addRepo:[GHVRepo new]];
!
// Assert the number of repos is now one
XCTAssertEqual([self.collection.repos count], 1,
@"Expected one repository");
}
@end
Example of iOS TDD Using XCTest
// Create an XCTestCase subclass
@interface GHVRepoCollectionTests : XCTestCase
// The subject of our tests
@property (nonatomic, strong) GHVCollection *collection;
@end
!
@implementation GHVRepoCollectionTests
// Set up the `repos` collection for each test
- (void)setUp {
[super setUp];
self.collection = [GHVCollection new];
}
// Add a test
- (void)testAddingARepo {
// Add a repo
[self.collection addRepo:[GHVRepo new]];
!
// Assert the number of repos is now one
XCTAssertEqual([self.collection.repos count], 1,
@"Expected one repository");
}
@end
Behavior-Driven Development
• Answers the question: “What do I test?”
• Behavioral tests don’t test the implementation, they

specify the behavior
iOS BDD Using Kiwi
iOS BDD Using Kiwi
// Describe how the `GHVCollection` class behaves
describe(@"GHVCollection", ^{
// Setup up the collection for each test
__block GHVCollection *collection = nil;
beforeEach(^{
collection = [GHVCollection new];
});
!
// Describe how the `-addRepo:` method behaves
describe(@"-addRepo:", ^{
context(@"after adding a repo", ^{
// Add a repo before each test
beforeEach(^{
[collection addRepo:[GHVRepo new]];
});
// Test the method behaves correctly
it(@"has a repo count of one", ^{
[[collection.repos should] haveCountOf:1];
});
});
});
});
iOS BDD Using Kiwi
// Describe how the `GHVCollection` class behaves
describe(@"GHVCollection", ^{
// Setup up the collection for each test
__block GHVCollection *collection = nil;
beforeEach(^{
collection = [GHVCollection new];
});
!
// Describe how the `-addRepo:` method behaves
describe(@"-addRepo:", ^{
context(@"after adding a repo", ^{
// Add a repo before each test
beforeEach(^{
[collection addRepo:[GHVRepo new]];
});
// Test the method behaves correctly
it(@"has a repo count of one", ^{
[[collection.repos should] haveCountOf:1];
});
});
});
});
iOS BDD Using Kiwi
// Describe how the `GHVCollection` class behaves
describe(@"GHVCollection", ^{
// Setup up the collection for each test
__block GHVCollection *collection = nil;
beforeEach(^{
collection = [GHVCollection new];
});
!
// Describe how the `-addRepo:` method behaves
describe(@"-addRepo:", ^{
context(@"after adding a repo", ^{
// Add a repo before each test
beforeEach(^{
[collection addRepo:[GHVRepo new]];
});
// Test the method behaves correctly
it(@"has a repo count of one", ^{
[[collection.repos should] haveCountOf:1];
});
});
});
});
iOS BDD Using Kiwi
// Describe how the `GHVCollection` class behaves
describe(@"GHVCollection", ^{
// Setup up the collection for each test
__block GHVCollection *collection = nil;
beforeEach(^{
collection = [GHVCollection new];
});
!
// Describe how the `-addRepo:` method behaves
describe(@"-addRepo:", ^{
context(@"after adding a repo", ^{
// Add a repo before each test
beforeEach(^{
[collection addRepo:[GHVRepo new]];
});
// Test the method behaves correctly
it(@"has a repo count of one", ^{
[[collection.repos should] haveCountOf:1];
});
});
});
});
iOS BDD Using Kiwi
// Describe how the `GHVCollection` class behaves
describe(@"GHVCollection", ^{
// Setup up the collection for each test
__block GHVCollection *collection = nil;
beforeEach(^{
collection = [GHVCollection new];
});
!
// Describe how the `-addRepo:` method behaves
describe(@"-addRepo:", ^{
context(@"after adding a repo", ^{
// Add a repo before each test
beforeEach(^{
[collection addRepo:[GHVRepo new]];
});
// Test the method behaves correctly
it(@"has a repo count of one", ^{
[[collection.repos should] haveCountOf:1];
});
});
});
});
iOS BDD Using Kiwi
// Describe how the `GHVCollection` class behaves
describe(@"GHVCollection", ^{
// Setup up the collection for each test
__block GHVCollection *collection = nil;
beforeEach(^{
collection = [GHVCollection new];
});
!
// Describe how the `-addRepo:` method behaves
describe(@"-addRepo:", ^{
context(@"after adding a repo", ^{
// Add a repo before each test
beforeEach(^{
[collection addRepo:[GHVRepo new]];
});
// Test the method behaves correctly
it(@"has a repo count of one", ^{
[[collection.repos should] haveCountOf:1];
});
});
});
});
Kiwi Benefits
Kiwi Benefits
• An unlimited amount of setup and teardown
Kiwi Benefits
• An unlimited amount of setup and teardown

beforeEach(^{
beforeAll(^{
afterEach(^{
afterAll(^{

/*
/*
/*
/*

...
...
...
...

*/
*/
*/
*/

});
});
});
});
Kiwi Benefits
• An unlimited amount of setup and teardown

beforeEach(^{
beforeAll(^{
afterEach(^{
afterAll(^{
• Mocks and stubs included

/*
/*
/*
/*

...
...
...
...

*/
*/
*/
*/

});
});
});
});
Kiwi Benefits
• An unlimited amount of setup and teardown

beforeEach(^{
beforeAll(^{
afterEach(^{
afterAll(^{

/*
/*
/*
/*

...
...
...
...

*/
*/
*/
*/

});
});
});
});

• Mocks and stubs included

[collection stub:@selector(addRepo:)];
Kiwi Benefits
• An unlimited amount of setup and teardown

beforeEach(^{
beforeAll(^{
afterEach(^{
afterAll(^{

/*
/*
/*
/*

...
...
...
...

*/
*/
*/
*/

});
});
});
});

• Mocks and stubs included

[collection stub:@selector(addRepo:)];
• Asynchronous testing support
Kiwi Benefits
• An unlimited amount of setup and teardown

beforeEach(^{
beforeAll(^{
afterEach(^{
afterAll(^{

/*
/*
/*
/*

...
...
...
...

*/
*/
*/
*/

});
});
});
});

• Mocks and stubs included

[collection stub:@selector(addRepo:)];
• Asynchronous testing support

[[collection.repos shouldEventually] haveCountOf:2];
Kiwi Benefits
• An unlimited amount of setup and teardown

beforeEach(^{
beforeAll(^{
afterEach(^{
afterAll(^{

/*
/*
/*
/*

...
...
...
...

*/
*/
*/
*/

});
});
});
});

• Mocks and stubs included

[collection stub:@selector(addRepo:)];
• Asynchronous testing support

[[collection.repos shouldEventually] haveCountOf:2];
• More readable than XCTest
Our First Failing Test
Our First Failing Test
/// GHVAPIClientSpec.m
!

it(@"gets repositories", ^{
// The repos returned by the API
__block NSArray *allRepos = nil;
!

// Fetch the repos from the API
[client allRepositoriesForUsername:@"modocache"
success:^(NSArray *repos) {
// Set the repos
allRepos = repos;
} failure:nil];
!

// Assert that the repos have been set
[[expectFutureValue(allRepos) shouldEventually]
haveCountOf:10];
});
Our First Failing Test
/// GHVAPIClientSpec.m
!

it(@"gets repositories", ^{
// The repos returned by the API
__block NSArray *allRepos = nil;
!

// Fetch the repos from the API
[client allRepositoriesForUsername:@"modocache"
success:^(NSArray *repos) {
// Set the repos
allRepos = repos;
} failure:nil];
!

// Assert that the repos have been set
[[expectFutureValue(allRepos) shouldEventually]
haveCountOf:10];
});
Our First Failing Test
/// GHVAPIClientSpec.m
!

it(@"gets repositories", ^{
// The repos returned by the API
__block NSArray *allRepos = nil;
!

// Fetch the repos from the API
[client allRepositoriesForUsername:@"modocache"
success:^(NSArray *repos) {
// Set the repos
allRepos = repos;
} failure:nil];
!

// Assert that the repos have been set
[[expectFutureValue(allRepos) shouldEventually]
haveCountOf:10];
});
Our First Failing Test
/// GHVAPIClientSpec.m
!

it(@"gets repositories", ^{
// The repos returned by the API
__block NSArray *allRepos = nil;
!

// Fetch the repos from the API
[client allRepositoriesForUsername:@"modocache"
success:^(NSArray *repos) {
// Set the repos
allRepos = repos;
} failure:nil];
!

// Assert that the repos have been set
[[expectFutureValue(allRepos) shouldEventually]
haveCountOf:10];
});
Our First Failing Test
/// GHVAPIClientSpec.m
!

it(@"gets repositories", ^{
// The repos returned by the API
__block NSArray *allRepos = nil;
!

// Fetch the repos from the API
[client allRepositoriesForUsername:@"modocache"
success:^(NSArray *repos) {
// Set the repos
allRepos = repos;
} failure:nil];
!

// Assert that the repos have been set
[[expectFutureValue(allRepos) shouldEventually]
haveCountOf:10];
});
Our First Failing Test
/// GHVAPIClientSpec.m
!

it(@"gets repositories", ^{
// The repos returned by the API
__block NSArray *allRepos = nil;
!

// Fetch the repos from the API
[client allRepositoriesForUsername:@"modocache"
success:^(NSArray *repos) {
// Set the repos
allRepos = repos;
} failure:nil];
!

// Assert that the repos have been set
[[expectFutureValue(allRepos) shouldEventually]
haveCountOf:10];
});
Going Green
Going Green
/// GHVAPIClient.m

!

// Create a request operation manager pointing at the GitHub API
NSString *urlString = @"https://api.github.com/";
NSURL *baseURL = [NSURL URLWithString:urlString];
AFHTTPRequestOperationManager *manager =
[[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL];

!

// The manager should serialize the response as JSON
manager.requestSerializer = [AFJSONRequestSerializer serializer];

!

// Send a request to GET /users/:username/repos
[manager GET:[NSString stringWithFormat:@"users/%@/repos",
username]
parameters:nil
success:^(AFHTTPRequestOperation *operation,
id responseObject) {
// Send response object to success block
success(responseObject);
}
failure:nil];
}
Going Green
/// GHVAPIClient.m

!

// Create a request operation manager pointing at the GitHub API
NSString *urlString = @"https://api.github.com/";
NSURL *baseURL = [NSURL URLWithString:urlString];
AFHTTPRequestOperationManager *manager =
[[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL];

!

// The manager should serialize the response as JSON
manager.requestSerializer = [AFJSONRequestSerializer serializer];

!

// Send a request to GET /users/:username/repos
[manager GET:[NSString stringWithFormat:@"users/%@/repos",
username]
parameters:nil
success:^(AFHTTPRequestOperation *operation,
id responseObject) {
// Send response object to success block
success(responseObject);
}
failure:nil];
}
Going Green
/// GHVAPIClient.m

!

// Create a request operation manager pointing at the GitHub API
NSString *urlString = @"https://api.github.com/";
NSURL *baseURL = [NSURL URLWithString:urlString];
AFHTTPRequestOperationManager *manager =
[[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL];

!

// The manager should serialize the response as JSON
manager.requestSerializer = [AFJSONRequestSerializer serializer];

!

// Send a request to GET /users/:username/repos
[manager GET:[NSString stringWithFormat:@"users/%@/repos",
username]
parameters:nil
success:^(AFHTTPRequestOperation *operation,
id responseObject) {
// Send response object to success block
success(responseObject);
}
failure:nil];
}
Going Green
/// GHVAPIClient.m

!

// Create a request operation manager pointing at the GitHub API
NSString *urlString = @"https://api.github.com/";
NSURL *baseURL = [NSURL URLWithString:urlString];
AFHTTPRequestOperationManager *manager =
[[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL];

!

// The manager should serialize the response as JSON
manager.requestSerializer = [AFJSONRequestSerializer serializer];

!

// Send a request to GET /users/:username/repos
[manager GET:[NSString stringWithFormat:@"users/%@/repos",
username]
parameters:nil
success:^(AFHTTPRequestOperation *operation,
id responseObject) {
// Send response object to success block
success(responseObject);
}
failure:nil];
}
Going Green
/// GHVAPIClient.m

!

// Create a request operation manager pointing at the GitHub API
NSString *urlString = @"https://api.github.com/";
NSURL *baseURL = [NSURL URLWithString:urlString];
AFHTTPRequestOperationManager *manager =
[[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL];

!

// The manager should serialize the response as JSON
manager.requestSerializer = [AFJSONRequestSerializer serializer];

!

// Send a request to GET /users/:username/repos
[manager GET:[NSString stringWithFormat:@"users/%@/repos",
username]
parameters:nil
success:^(AFHTTPRequestOperation *operation,
id responseObject) {
// Send response object to success block
success(responseObject);
}
failure:nil];
}
Problems with our Test
• The test has external dependencies
• It’ll fail if the GitHub API is down
• It’ll fail if run without an internet connection
• It’ll fail if the response is too slow
• The test is slow
• It sends a request every time it’s run
HTTP Stubbing
Eliminating external dependencies
stubRequest(@"GET",
@“https://api.github.com/"
@“users/modocache/repos")
.andReturn(200)
.withHeaders(@{@"Content-Type": @"application/json"})
.withBody(@"["repo-1"]");
!

GHVAPIClient *client = [GHVAPIClient new];
!

// ...
!

[[expectFutureValue(allRepos) shouldEventually]
haveCountOf:1];
HTTP Stubbing
Eliminating external dependencies
stubRequest(@"GET",
@“https://api.github.com/"
@“users/modocache/repos")
.andReturn(200)
.withHeaders(@{@"Content-Type": @"application/json"})
.withBody(@"["repo-1"]");
!

GHVAPIClient *client = [GHVAPIClient new];
!

// ...
!

[[expectFutureValue(allRepos) shouldEventually]
haveCountOf:1];
HTTP Stubbing
Eliminating external dependencies
stubRequest(@"GET",
@“https://api.github.com/"
@“users/modocache/repos")
.andReturn(200)
.withHeaders(@{@"Content-Type": @"application/json"})
.withBody(@"["repo-1"]");
!

GHVAPIClient *client = [GHVAPIClient new];
!

// ...
!

[[expectFutureValue(allRepos) shouldEventually]
haveCountOf:1];
HTTP Stubbing
Eliminating external dependencies
stubRequest(@"GET",
@“https://api.github.com/"
@“users/modocache/repos")
.andReturn(200)
.withHeaders(@{@"Content-Type": @"application/json"})
.withBody(@"["repo-1"]");
!

GHVAPIClient *client = [GHVAPIClient new];
!

// ...
!

[[expectFutureValue(allRepos) shouldEventually]
haveCountOf:1];
HTTP Stubbing
Eliminating external dependencies
stubRequest(@"GET",
@“https://api.github.com/"
@“users/modocache/repos")
.andReturn(200)
.withHeaders(@{@"Content-Type": @"application/json"})
.withBody(@"["repo-1"]");
!

GHVAPIClient *client = [GHVAPIClient new];
!

// ...
!

[[expectFutureValue(allRepos) shouldEventually]
haveCountOf:1];
HTTP Stubbing
Eliminating external dependencies
stubRequest(@"GET",
@“https://api.github.com/"
@“users/modocache/repos")
.andReturn(200)
.withHeaders(@{@"Content-Type": @"application/json"})
.withBody(@"["repo-1"]");
!

GHVAPIClient *client = [GHVAPIClient new];
!

// ...
!

[[expectFutureValue(allRepos) shouldEventually]
haveCountOf:1];
Problems Nocilla Fixes
• The test no longer has external dependencies
• It’ll pass whether the GitHub API is online or not
• It’ll pass even when run offline
• The test is fast
• It still sends a request, but that request is immediately

intercepted and a response is returned
Other Nocilla Features
Other Nocilla Features
• Stub HTTP requests using regular expressions
Other Nocilla Features
• Stub HTTP requests using regular expressions
stubRequest(@"GET",
@"https://api.github.com/"
@"users/(.*?)/repos".regex)
Other Nocilla Features
• Stub HTTP requests using regular expressions
stubRequest(@"GET",
@"https://api.github.com/"
@"users/(.*?)/repos".regex)

• Return errors, such as for poor internet connection
Other Nocilla Features
• Stub HTTP requests using regular expressions
stubRequest(@"GET",
@"https://api.github.com/"
@"users/(.*?)/repos".regex)

• Return errors, such as for poor internet connection
NSError *error =
[NSError errorWithDomain:NSURLErrorDomain
code:29
userInfo:@{NSLocalizedDescriptionKey: @"Uh-oh!"}];
stubRequest(@"GET", @"...")
.andFailWithError(error);
Takeaways
Takeaways
• Readable, behavior-driven, asynchronous tests with Kiwi
• https://github.com/allending/Kiwi
Takeaways
• Readable, behavior-driven, asynchronous tests with Kiwi
• https://github.com/allending/Kiwi

pod "Kiwi/XCTest"
Takeaways
• Readable, behavior-driven, asynchronous tests with Kiwi
• https://github.com/allending/Kiwi

pod "Kiwi/XCTest"
• Eliminate network dependencies with Nocilla
• https://github.com/luisobo/Nocilla
Takeaways
• Readable, behavior-driven, asynchronous tests with Kiwi
• https://github.com/allending/Kiwi

pod "Kiwi/XCTest"
• Eliminate network dependencies with Nocilla
• https://github.com/luisobo/Nocilla

pod "Nocilla"
Questions?
@modocache #startup_ios
Questions?
@modocache #startup_ios

describe(@"this talk", ^{
context(@"after presenting the slides", ^{
it(@"moves to Q&A", ^{
[[you should] askQuestions];
[[you shouldEventually]
receive:@selector(stop)];
});
});
});
Questions?
@modocache #startup_ios

describe(@"this talk", ^{
context(@"after presenting the slides", ^{
it(@"moves to Q&A", ^{
[[you should] askQuestions];
[[you shouldEventually]
receive:@selector(stop)];
});
});
});
Questions?
@modocache #startup_ios

describe(@"this talk", ^{
context(@"after presenting the slides", ^{
it(@"moves to Q&A", ^{
[[you should] askQuestions];
[[you shouldEventually]
receive:@selector(stop)];
});
});
});
Questions?
@modocache #startup_ios

describe(@"this talk", ^{
context(@"after presenting the slides", ^{
it(@"moves to Q&A", ^{
[[you should] askQuestions];
[[you shouldEventually]
receive:@selector(stop)];
});
});
});
Questions?
@modocache #startup_ios

describe(@"this talk", ^{
context(@"after presenting the slides", ^{
it(@"moves to Q&A", ^{
[[you should] askQuestions];
[[you shouldEventually]
receive:@selector(stop)];
});
});
});

Mais conteúdo relacionado

Mais procurados

Custom deployments with sbt-native-packager
Custom deployments with sbt-native-packagerCustom deployments with sbt-native-packager
Custom deployments with sbt-native-packagerGaryCoady
 
Spring & Hibernate
Spring & HibernateSpring & Hibernate
Spring & HibernateJiayun Zhou
 
Revolution or Evolution in Page Object
Revolution or Evolution in Page ObjectRevolution or Evolution in Page Object
Revolution or Evolution in Page ObjectArtem Sokovets
 
Continous delivery with sbt
Continous delivery with sbtContinous delivery with sbt
Continous delivery with sbtWojciech Pituła
 
EPAM IT WEEK: AEM & TDD. It's so boring...
EPAM IT WEEK: AEM & TDD. It's so boring...EPAM IT WEEK: AEM & TDD. It's so boring...
EPAM IT WEEK: AEM & TDD. It's so boring...Andrew Manuev
 
React, Redux and es6/7
React, Redux and es6/7React, Redux and es6/7
React, Redux and es6/7Dongho Cho
 
Basic Tutorial of React for Programmers
Basic Tutorial of React for ProgrammersBasic Tutorial of React for Programmers
Basic Tutorial of React for ProgrammersDavid Rodenas
 
RDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по DipRDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по DipRAMBLER&Co
 
Application Frameworks: The new kids on the block
Application Frameworks: The new kids on the blockApplication Frameworks: The new kids on the block
Application Frameworks: The new kids on the blockRichard Lord
 
Test-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS ApplicationsTest-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS ApplicationsFITC
 
Testing your javascript code with jasmine
Testing your javascript code with jasmineTesting your javascript code with jasmine
Testing your javascript code with jasmineRubyc Slides
 
[Srijan Wednesday Webinar] Rails 5: What's in It for Me?
[Srijan Wednesday Webinar] Rails 5: What's in It for Me?[Srijan Wednesday Webinar] Rails 5: What's in It for Me?
[Srijan Wednesday Webinar] Rails 5: What's in It for Me?Srijan Technologies
 
連邦の白いヤツ 「Objective-C」
連邦の白いヤツ 「Objective-C」連邦の白いヤツ 「Objective-C」
連邦の白いヤツ 「Objective-C」matuura_core
 
GeeCON 2017 - TestContainers. Integration testing without the hassle
GeeCON 2017 - TestContainers. Integration testing without the hassleGeeCON 2017 - TestContainers. Integration testing without the hassle
GeeCON 2017 - TestContainers. Integration testing without the hassleAnton Arhipov
 
Adventures In JavaScript Testing
Adventures In JavaScript TestingAdventures In JavaScript Testing
Adventures In JavaScript TestingThomas Fuchs
 

Mais procurados (20)

Custom deployments with sbt-native-packager
Custom deployments with sbt-native-packagerCustom deployments with sbt-native-packager
Custom deployments with sbt-native-packager
 
Spring & Hibernate
Spring & HibernateSpring & Hibernate
Spring & Hibernate
 
SonarQube for AEM
SonarQube for AEMSonarQube for AEM
SonarQube for AEM
 
The JavaFX Ecosystem
The JavaFX EcosystemThe JavaFX Ecosystem
The JavaFX Ecosystem
 
Revolution or Evolution in Page Object
Revolution or Evolution in Page ObjectRevolution or Evolution in Page Object
Revolution or Evolution in Page Object
 
Continous delivery with sbt
Continous delivery with sbtContinous delivery with sbt
Continous delivery with sbt
 
Agile Android
Agile AndroidAgile Android
Agile Android
 
OneRing @ OSCamp 2010
OneRing @ OSCamp 2010OneRing @ OSCamp 2010
OneRing @ OSCamp 2010
 
Agile Swift
Agile SwiftAgile Swift
Agile Swift
 
EPAM IT WEEK: AEM & TDD. It's so boring...
EPAM IT WEEK: AEM & TDD. It's so boring...EPAM IT WEEK: AEM & TDD. It's so boring...
EPAM IT WEEK: AEM & TDD. It's so boring...
 
React, Redux and es6/7
React, Redux and es6/7React, Redux and es6/7
React, Redux and es6/7
 
Basic Tutorial of React for Programmers
Basic Tutorial of React for ProgrammersBasic Tutorial of React for Programmers
Basic Tutorial of React for Programmers
 
RDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по DipRDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по Dip
 
Application Frameworks: The new kids on the block
Application Frameworks: The new kids on the blockApplication Frameworks: The new kids on the block
Application Frameworks: The new kids on the block
 
Test-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS ApplicationsTest-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS Applications
 
Testing your javascript code with jasmine
Testing your javascript code with jasmineTesting your javascript code with jasmine
Testing your javascript code with jasmine
 
[Srijan Wednesday Webinar] Rails 5: What's in It for Me?
[Srijan Wednesday Webinar] Rails 5: What's in It for Me?[Srijan Wednesday Webinar] Rails 5: What's in It for Me?
[Srijan Wednesday Webinar] Rails 5: What's in It for Me?
 
連邦の白いヤツ 「Objective-C」
連邦の白いヤツ 「Objective-C」連邦の白いヤツ 「Objective-C」
連邦の白いヤツ 「Objective-C」
 
GeeCON 2017 - TestContainers. Integration testing without the hassle
GeeCON 2017 - TestContainers. Integration testing without the hassleGeeCON 2017 - TestContainers. Integration testing without the hassle
GeeCON 2017 - TestContainers. Integration testing without the hassle
 
Adventures In JavaScript Testing
Adventures In JavaScript TestingAdventures In JavaScript Testing
Adventures In JavaScript Testing
 

Destaque

Intel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance ProgrammingIntel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance ProgrammingBrian Gesiak
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API DesignBrian Gesiak
 
アップルのテンプレートは有害と考えられる
アップルのテンプレートは有害と考えられるアップルのテンプレートは有害と考えられる
アップルのテンプレートは有害と考えられるBrian Gesiak
 
iOSビヘイビア駆動開発
iOSビヘイビア駆動開発iOSビヘイビア駆動開発
iOSビヘイビア駆動開発Brian Gesiak
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API DesignBrian Gesiak
 
Mobile Application Testing Process
Mobile Application Testing ProcessMobile Application Testing Process
Mobile Application Testing ProcessAeroqube
 
Behavior driven development for Mobile apps
Behavior driven development for Mobile appsBehavior driven development for Mobile apps
Behavior driven development for Mobile appsGeert van der Cruijsen
 
Creating A Product Backlog
Creating A Product BacklogCreating A Product Backlog
Creating A Product BacklogRussell Pannone
 

Destaque (9)

Intel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance ProgrammingIntel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance Programming
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API Design
 
アップルのテンプレートは有害と考えられる
アップルのテンプレートは有害と考えられるアップルのテンプレートは有害と考えられる
アップルのテンプレートは有害と考えられる
 
iOSビヘイビア駆動開発
iOSビヘイビア駆動開発iOSビヘイビア駆動開発
iOSビヘイビア駆動開発
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API Design
 
Mobile Application Testing Process
Mobile Application Testing ProcessMobile Application Testing Process
Mobile Application Testing Process
 
Behavior driven development for Mobile apps
Behavior driven development for Mobile appsBehavior driven development for Mobile apps
Behavior driven development for Mobile apps
 
The Executioner's Tale
The Executioner's TaleThe Executioner's Tale
The Executioner's Tale
 
Creating A Product Backlog
Creating A Product BacklogCreating A Product Backlog
Creating A Product Backlog
 

Semelhante a iOS Behavior-Driven Development

Javascript tdd byandreapaciolla
Javascript tdd byandreapaciollaJavascript tdd byandreapaciolla
Javascript tdd byandreapaciollaAndrea Paciolla
 
Android Automated Testing
Android Automated TestingAndroid Automated Testing
Android Automated Testingroisagiv
 
Javascript Everywhere
Javascript EverywhereJavascript Everywhere
Javascript EverywherePascal Rettig
 
Writing native bindings to node.js in C++
Writing native bindings to node.js in C++Writing native bindings to node.js in C++
Writing native bindings to node.js in C++nsm.nikhil
 
Infinum Android Talks #17 - Testing your Android applications by Ivan Kust
Infinum Android Talks #17 - Testing your Android applications by Ivan KustInfinum Android Talks #17 - Testing your Android applications by Ivan Kust
Infinum Android Talks #17 - Testing your Android applications by Ivan KustInfinum
 
How and why i roll my own node.js framework
How and why i roll my own node.js frameworkHow and why i roll my own node.js framework
How and why i roll my own node.js frameworkBen Lin
 
First adoption hackathon at BGJUG
First adoption hackathon at BGJUGFirst adoption hackathon at BGJUG
First adoption hackathon at BGJUGIvan Ivanov
 
Gradleintroduction 111010130329-phpapp01
Gradleintroduction 111010130329-phpapp01Gradleintroduction 111010130329-phpapp01
Gradleintroduction 111010130329-phpapp01Tino Isnich
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockRobot Media
 
A few good JavaScript development tools
A few good JavaScript development toolsA few good JavaScript development tools
A few good JavaScript development toolsSimon Kim
 
Testing Java Code Effectively
Testing Java Code EffectivelyTesting Java Code Effectively
Testing Java Code EffectivelyAndres Almiray
 
Description (Part A) In this lab you will write a Queue implementati.pdf
Description (Part A) In this lab you will write a Queue implementati.pdfDescription (Part A) In this lab you will write a Queue implementati.pdf
Description (Part A) In this lab you will write a Queue implementati.pdfrishabjain5053
 
CP3108B (Mozilla) Sharing Session on Add-on SDK
CP3108B (Mozilla) Sharing Session on Add-on SDKCP3108B (Mozilla) Sharing Session on Add-on SDK
CP3108B (Mozilla) Sharing Session on Add-on SDKMifeng
 
Testing the Enterprise Layers - the A, B, C's of Integration Testing - Aslak ...
Testing the Enterprise Layers - the A, B, C's of Integration Testing - Aslak ...Testing the Enterprise Layers - the A, B, C's of Integration Testing - Aslak ...
Testing the Enterprise Layers - the A, B, C's of Integration Testing - Aslak ...JAXLondon2014
 
Testing the Enterprise layers, with Arquillian
Testing the Enterprise layers, with ArquillianTesting the Enterprise layers, with Arquillian
Testing the Enterprise layers, with ArquillianVirtual JBoss User Group
 
Taking a Test Drive
Taking a Test DriveTaking a Test Drive
Taking a Test DriveGraham Lee
 
Java Day Kharkiv - Integration Testing from the Trenches Rebooted
Java Day Kharkiv - Integration Testing from the Trenches RebootedJava Day Kharkiv - Integration Testing from the Trenches Rebooted
Java Day Kharkiv - Integration Testing from the Trenches RebootedNicolas Fränkel
 

Semelhante a iOS Behavior-Driven Development (20)

Javascript tdd byandreapaciolla
Javascript tdd byandreapaciollaJavascript tdd byandreapaciolla
Javascript tdd byandreapaciolla
 
Android Automated Testing
Android Automated TestingAndroid Automated Testing
Android Automated Testing
 
Javascript Everywhere
Javascript EverywhereJavascript Everywhere
Javascript Everywhere
 
Writing native bindings to node.js in C++
Writing native bindings to node.js in C++Writing native bindings to node.js in C++
Writing native bindings to node.js in C++
 
Infinum Android Talks #17 - Testing your Android applications by Ivan Kust
Infinum Android Talks #17 - Testing your Android applications by Ivan KustInfinum Android Talks #17 - Testing your Android applications by Ivan Kust
Infinum Android Talks #17 - Testing your Android applications by Ivan Kust
 
How and why i roll my own node.js framework
How and why i roll my own node.js frameworkHow and why i roll my own node.js framework
How and why i roll my own node.js framework
 
First adoption hackathon at BGJUG
First adoption hackathon at BGJUGFirst adoption hackathon at BGJUG
First adoption hackathon at BGJUG
 
Vuejs testing
Vuejs testingVuejs testing
Vuejs testing
 
Gradle Introduction
Gradle IntroductionGradle Introduction
Gradle Introduction
 
Gradleintroduction 111010130329-phpapp01
Gradleintroduction 111010130329-phpapp01Gradleintroduction 111010130329-phpapp01
Gradleintroduction 111010130329-phpapp01
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
 
A few good JavaScript development tools
A few good JavaScript development toolsA few good JavaScript development tools
A few good JavaScript development tools
 
Testing Java Code Effectively
Testing Java Code EffectivelyTesting Java Code Effectively
Testing Java Code Effectively
 
Corba
CorbaCorba
Corba
 
Description (Part A) In this lab you will write a Queue implementati.pdf
Description (Part A) In this lab you will write a Queue implementati.pdfDescription (Part A) In this lab you will write a Queue implementati.pdf
Description (Part A) In this lab you will write a Queue implementati.pdf
 
CP3108B (Mozilla) Sharing Session on Add-on SDK
CP3108B (Mozilla) Sharing Session on Add-on SDKCP3108B (Mozilla) Sharing Session on Add-on SDK
CP3108B (Mozilla) Sharing Session on Add-on SDK
 
Testing the Enterprise Layers - the A, B, C's of Integration Testing - Aslak ...
Testing the Enterprise Layers - the A, B, C's of Integration Testing - Aslak ...Testing the Enterprise Layers - the A, B, C's of Integration Testing - Aslak ...
Testing the Enterprise Layers - the A, B, C's of Integration Testing - Aslak ...
 
Testing the Enterprise layers, with Arquillian
Testing the Enterprise layers, with ArquillianTesting the Enterprise layers, with Arquillian
Testing the Enterprise layers, with Arquillian
 
Taking a Test Drive
Taking a Test DriveTaking a Test Drive
Taking a Test Drive
 
Java Day Kharkiv - Integration Testing from the Trenches Rebooted
Java Day Kharkiv - Integration Testing from the Trenches RebootedJava Day Kharkiv - Integration Testing from the Trenches Rebooted
Java Day Kharkiv - Integration Testing from the Trenches Rebooted
 

Último

Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksSoftradix Technologies
 
Pigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Patryk Bandurski
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 3652toLead Limited
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Allon Mureinik
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...HostedbyConfluent
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationSafe Software
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?XfilesPro
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitecturePixlogix Infotech
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Alan Dix
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
Pigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
 

Último (20)

Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other Frameworks
 
Pigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping Elbows
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC Architecture
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
Pigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food Manufacturing
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
 

iOS Behavior-Driven Development

  • 1. iOS Behavior-Driven Development Testing RESTful Applications with Kiwi and Nocilla March 9th, 2014 Brian Gesiak Research Student, The University of Tokyo @modocache #startup_ios
  • 2. Today • Behavior-driven development (BDD) • iOS behavior-driven development • Kiwi • Testing asynchronous networking • Nocilla
  • 3. Test-Driving Network Code Motivation • Let’s say we want to display a user’s repositories on GitHub • We can GET JSON from the GitHub API • https://api.github.com/users/ {{ username }}/repos.json
  • 4. Test-Driving Network Code Motivation /// GET /users/:username/repos ! [ { "id": 1296269, "name": "Hello-World", "description": "My first repo!", /* ... */ } ]
  • 6. Building the App Behavior-Driven Development Using Kiwi • Behavior-driven development (BDD) is an extension of test-driven development
  • 8. Test-Driven Development • Red: Write a test and watch it fail
  • 9. Test-Driven Development • Red: Write a test and watch it fail • Green: Pass the test (by writing as little code as possible)
  • 10. Test-Driven Development • Red: Write a test and watch it fail • Green: Pass the test (by writing as little code as possible) • Refactor: Remove duplication
  • 11. Test-Driven Development • Red: Write a test and watch it fail • Green: Pass the test (by writing as little code as possible) • Refactor: Remove duplication • Repeat
  • 12. Example of iOS TDD Using XCTest
  • 13. Example of iOS TDD Using XCTest // Create an XCTestCase subclass @interface GHVRepoCollectionTests : XCTestCase // The subject of our tests @property (nonatomic, strong) GHVCollection *collection; @end ! @implementation GHVRepoCollectionTests // Set up the `repos` collection for each test - (void)setUp { [super setUp]; self.collection = [GHVCollection new]; } // Add a test - (void)testAddingARepo { // Add a repo [self.collection addRepo:[GHVRepo new]]; ! // Assert the number of repos is now one XCTAssertEqual([self.collection.repos count], 1, @"Expected one repository"); } @end
  • 14. Example of iOS TDD Using XCTest // Create an XCTestCase subclass @interface GHVRepoCollectionTests : XCTestCase // The subject of our tests @property (nonatomic, strong) GHVCollection *collection; @end ! @implementation GHVRepoCollectionTests // Set up the `repos` collection for each test - (void)setUp { [super setUp]; self.collection = [GHVCollection new]; } // Add a test - (void)testAddingARepo { // Add a repo [self.collection addRepo:[GHVRepo new]]; ! // Assert the number of repos is now one XCTAssertEqual([self.collection.repos count], 1, @"Expected one repository"); } @end
  • 15. Example of iOS TDD Using XCTest // Create an XCTestCase subclass @interface GHVRepoCollectionTests : XCTestCase // The subject of our tests @property (nonatomic, strong) GHVCollection *collection; @end ! @implementation GHVRepoCollectionTests // Set up the `repos` collection for each test - (void)setUp { [super setUp]; self.collection = [GHVCollection new]; } // Add a test - (void)testAddingARepo { // Add a repo [self.collection addRepo:[GHVRepo new]]; ! // Assert the number of repos is now one XCTAssertEqual([self.collection.repos count], 1, @"Expected one repository"); } @end
  • 16. Example of iOS TDD Using XCTest // Create an XCTestCase subclass @interface GHVRepoCollectionTests : XCTestCase // The subject of our tests @property (nonatomic, strong) GHVCollection *collection; @end ! @implementation GHVRepoCollectionTests // Set up the `repos` collection for each test - (void)setUp { [super setUp]; self.collection = [GHVCollection new]; } // Add a test - (void)testAddingARepo { // Add a repo [self.collection addRepo:[GHVRepo new]]; ! // Assert the number of repos is now one XCTAssertEqual([self.collection.repos count], 1, @"Expected one repository"); } @end
  • 17. Example of iOS TDD Using XCTest // Create an XCTestCase subclass @interface GHVRepoCollectionTests : XCTestCase // The subject of our tests @property (nonatomic, strong) GHVCollection *collection; @end ! @implementation GHVRepoCollectionTests // Set up the `repos` collection for each test - (void)setUp { [super setUp]; self.collection = [GHVCollection new]; } // Add a test - (void)testAddingARepo { // Add a repo [self.collection addRepo:[GHVRepo new]]; ! // Assert the number of repos is now one XCTAssertEqual([self.collection.repos count], 1, @"Expected one repository"); } @end
  • 18. Example of iOS TDD Using XCTest // Create an XCTestCase subclass @interface GHVRepoCollectionTests : XCTestCase // The subject of our tests @property (nonatomic, strong) GHVCollection *collection; @end ! @implementation GHVRepoCollectionTests // Set up the `repos` collection for each test - (void)setUp { [super setUp]; self.collection = [GHVCollection new]; } // Add a test - (void)testAddingARepo { // Add a repo [self.collection addRepo:[GHVRepo new]]; ! // Assert the number of repos is now one XCTAssertEqual([self.collection.repos count], 1, @"Expected one repository"); } @end
  • 19. Behavior-Driven Development • Answers the question: “What do I test?” • Behavioral tests don’t test the implementation, they specify the behavior
  • 21. iOS BDD Using Kiwi // Describe how the `GHVCollection` class behaves describe(@"GHVCollection", ^{ // Setup up the collection for each test __block GHVCollection *collection = nil; beforeEach(^{ collection = [GHVCollection new]; }); ! // Describe how the `-addRepo:` method behaves describe(@"-addRepo:", ^{ context(@"after adding a repo", ^{ // Add a repo before each test beforeEach(^{ [collection addRepo:[GHVRepo new]]; }); // Test the method behaves correctly it(@"has a repo count of one", ^{ [[collection.repos should] haveCountOf:1]; }); }); }); });
  • 22. iOS BDD Using Kiwi // Describe how the `GHVCollection` class behaves describe(@"GHVCollection", ^{ // Setup up the collection for each test __block GHVCollection *collection = nil; beforeEach(^{ collection = [GHVCollection new]; }); ! // Describe how the `-addRepo:` method behaves describe(@"-addRepo:", ^{ context(@"after adding a repo", ^{ // Add a repo before each test beforeEach(^{ [collection addRepo:[GHVRepo new]]; }); // Test the method behaves correctly it(@"has a repo count of one", ^{ [[collection.repos should] haveCountOf:1]; }); }); }); });
  • 23. iOS BDD Using Kiwi // Describe how the `GHVCollection` class behaves describe(@"GHVCollection", ^{ // Setup up the collection for each test __block GHVCollection *collection = nil; beforeEach(^{ collection = [GHVCollection new]; }); ! // Describe how the `-addRepo:` method behaves describe(@"-addRepo:", ^{ context(@"after adding a repo", ^{ // Add a repo before each test beforeEach(^{ [collection addRepo:[GHVRepo new]]; }); // Test the method behaves correctly it(@"has a repo count of one", ^{ [[collection.repos should] haveCountOf:1]; }); }); }); });
  • 24. iOS BDD Using Kiwi // Describe how the `GHVCollection` class behaves describe(@"GHVCollection", ^{ // Setup up the collection for each test __block GHVCollection *collection = nil; beforeEach(^{ collection = [GHVCollection new]; }); ! // Describe how the `-addRepo:` method behaves describe(@"-addRepo:", ^{ context(@"after adding a repo", ^{ // Add a repo before each test beforeEach(^{ [collection addRepo:[GHVRepo new]]; }); // Test the method behaves correctly it(@"has a repo count of one", ^{ [[collection.repos should] haveCountOf:1]; }); }); }); });
  • 25. iOS BDD Using Kiwi // Describe how the `GHVCollection` class behaves describe(@"GHVCollection", ^{ // Setup up the collection for each test __block GHVCollection *collection = nil; beforeEach(^{ collection = [GHVCollection new]; }); ! // Describe how the `-addRepo:` method behaves describe(@"-addRepo:", ^{ context(@"after adding a repo", ^{ // Add a repo before each test beforeEach(^{ [collection addRepo:[GHVRepo new]]; }); // Test the method behaves correctly it(@"has a repo count of one", ^{ [[collection.repos should] haveCountOf:1]; }); }); }); });
  • 26. iOS BDD Using Kiwi // Describe how the `GHVCollection` class behaves describe(@"GHVCollection", ^{ // Setup up the collection for each test __block GHVCollection *collection = nil; beforeEach(^{ collection = [GHVCollection new]; }); ! // Describe how the `-addRepo:` method behaves describe(@"-addRepo:", ^{ context(@"after adding a repo", ^{ // Add a repo before each test beforeEach(^{ [collection addRepo:[GHVRepo new]]; }); // Test the method behaves correctly it(@"has a repo count of one", ^{ [[collection.repos should] haveCountOf:1]; }); }); }); });
  • 28. Kiwi Benefits • An unlimited amount of setup and teardown
  • 29. Kiwi Benefits • An unlimited amount of setup and teardown beforeEach(^{ beforeAll(^{ afterEach(^{ afterAll(^{ /* /* /* /* ... ... ... ... */ */ */ */ }); }); }); });
  • 30. Kiwi Benefits • An unlimited amount of setup and teardown beforeEach(^{ beforeAll(^{ afterEach(^{ afterAll(^{ • Mocks and stubs included /* /* /* /* ... ... ... ... */ */ */ */ }); }); }); });
  • 31. Kiwi Benefits • An unlimited amount of setup and teardown beforeEach(^{ beforeAll(^{ afterEach(^{ afterAll(^{ /* /* /* /* ... ... ... ... */ */ */ */ }); }); }); }); • Mocks and stubs included [collection stub:@selector(addRepo:)];
  • 32. Kiwi Benefits • An unlimited amount of setup and teardown beforeEach(^{ beforeAll(^{ afterEach(^{ afterAll(^{ /* /* /* /* ... ... ... ... */ */ */ */ }); }); }); }); • Mocks and stubs included [collection stub:@selector(addRepo:)]; • Asynchronous testing support
  • 33. Kiwi Benefits • An unlimited amount of setup and teardown beforeEach(^{ beforeAll(^{ afterEach(^{ afterAll(^{ /* /* /* /* ... ... ... ... */ */ */ */ }); }); }); }); • Mocks and stubs included [collection stub:@selector(addRepo:)]; • Asynchronous testing support [[collection.repos shouldEventually] haveCountOf:2];
  • 34. Kiwi Benefits • An unlimited amount of setup and teardown beforeEach(^{ beforeAll(^{ afterEach(^{ afterAll(^{ /* /* /* /* ... ... ... ... */ */ */ */ }); }); }); }); • Mocks and stubs included [collection stub:@selector(addRepo:)]; • Asynchronous testing support [[collection.repos shouldEventually] haveCountOf:2]; • More readable than XCTest
  • 36. Our First Failing Test /// GHVAPIClientSpec.m ! it(@"gets repositories", ^{ // The repos returned by the API __block NSArray *allRepos = nil; ! // Fetch the repos from the API [client allRepositoriesForUsername:@"modocache" success:^(NSArray *repos) { // Set the repos allRepos = repos; } failure:nil]; ! // Assert that the repos have been set [[expectFutureValue(allRepos) shouldEventually] haveCountOf:10]; });
  • 37. Our First Failing Test /// GHVAPIClientSpec.m ! it(@"gets repositories", ^{ // The repos returned by the API __block NSArray *allRepos = nil; ! // Fetch the repos from the API [client allRepositoriesForUsername:@"modocache" success:^(NSArray *repos) { // Set the repos allRepos = repos; } failure:nil]; ! // Assert that the repos have been set [[expectFutureValue(allRepos) shouldEventually] haveCountOf:10]; });
  • 38. Our First Failing Test /// GHVAPIClientSpec.m ! it(@"gets repositories", ^{ // The repos returned by the API __block NSArray *allRepos = nil; ! // Fetch the repos from the API [client allRepositoriesForUsername:@"modocache" success:^(NSArray *repos) { // Set the repos allRepos = repos; } failure:nil]; ! // Assert that the repos have been set [[expectFutureValue(allRepos) shouldEventually] haveCountOf:10]; });
  • 39. Our First Failing Test /// GHVAPIClientSpec.m ! it(@"gets repositories", ^{ // The repos returned by the API __block NSArray *allRepos = nil; ! // Fetch the repos from the API [client allRepositoriesForUsername:@"modocache" success:^(NSArray *repos) { // Set the repos allRepos = repos; } failure:nil]; ! // Assert that the repos have been set [[expectFutureValue(allRepos) shouldEventually] haveCountOf:10]; });
  • 40. Our First Failing Test /// GHVAPIClientSpec.m ! it(@"gets repositories", ^{ // The repos returned by the API __block NSArray *allRepos = nil; ! // Fetch the repos from the API [client allRepositoriesForUsername:@"modocache" success:^(NSArray *repos) { // Set the repos allRepos = repos; } failure:nil]; ! // Assert that the repos have been set [[expectFutureValue(allRepos) shouldEventually] haveCountOf:10]; });
  • 41. Our First Failing Test /// GHVAPIClientSpec.m ! it(@"gets repositories", ^{ // The repos returned by the API __block NSArray *allRepos = nil; ! // Fetch the repos from the API [client allRepositoriesForUsername:@"modocache" success:^(NSArray *repos) { // Set the repos allRepos = repos; } failure:nil]; ! // Assert that the repos have been set [[expectFutureValue(allRepos) shouldEventually] haveCountOf:10]; });
  • 43. Going Green /// GHVAPIClient.m ! // Create a request operation manager pointing at the GitHub API NSString *urlString = @"https://api.github.com/"; NSURL *baseURL = [NSURL URLWithString:urlString]; AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL]; ! // The manager should serialize the response as JSON manager.requestSerializer = [AFJSONRequestSerializer serializer]; ! // Send a request to GET /users/:username/repos [manager GET:[NSString stringWithFormat:@"users/%@/repos", username] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { // Send response object to success block success(responseObject); } failure:nil]; }
  • 44. Going Green /// GHVAPIClient.m ! // Create a request operation manager pointing at the GitHub API NSString *urlString = @"https://api.github.com/"; NSURL *baseURL = [NSURL URLWithString:urlString]; AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL]; ! // The manager should serialize the response as JSON manager.requestSerializer = [AFJSONRequestSerializer serializer]; ! // Send a request to GET /users/:username/repos [manager GET:[NSString stringWithFormat:@"users/%@/repos", username] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { // Send response object to success block success(responseObject); } failure:nil]; }
  • 45. Going Green /// GHVAPIClient.m ! // Create a request operation manager pointing at the GitHub API NSString *urlString = @"https://api.github.com/"; NSURL *baseURL = [NSURL URLWithString:urlString]; AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL]; ! // The manager should serialize the response as JSON manager.requestSerializer = [AFJSONRequestSerializer serializer]; ! // Send a request to GET /users/:username/repos [manager GET:[NSString stringWithFormat:@"users/%@/repos", username] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { // Send response object to success block success(responseObject); } failure:nil]; }
  • 46. Going Green /// GHVAPIClient.m ! // Create a request operation manager pointing at the GitHub API NSString *urlString = @"https://api.github.com/"; NSURL *baseURL = [NSURL URLWithString:urlString]; AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL]; ! // The manager should serialize the response as JSON manager.requestSerializer = [AFJSONRequestSerializer serializer]; ! // Send a request to GET /users/:username/repos [manager GET:[NSString stringWithFormat:@"users/%@/repos", username] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { // Send response object to success block success(responseObject); } failure:nil]; }
  • 47. Going Green /// GHVAPIClient.m ! // Create a request operation manager pointing at the GitHub API NSString *urlString = @"https://api.github.com/"; NSURL *baseURL = [NSURL URLWithString:urlString]; AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL]; ! // The manager should serialize the response as JSON manager.requestSerializer = [AFJSONRequestSerializer serializer]; ! // Send a request to GET /users/:username/repos [manager GET:[NSString stringWithFormat:@"users/%@/repos", username] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { // Send response object to success block success(responseObject); } failure:nil]; }
  • 48. Problems with our Test • The test has external dependencies • It’ll fail if the GitHub API is down • It’ll fail if run without an internet connection • It’ll fail if the response is too slow • The test is slow • It sends a request every time it’s run
  • 49. HTTP Stubbing Eliminating external dependencies stubRequest(@"GET", @“https://api.github.com/" @“users/modocache/repos") .andReturn(200) .withHeaders(@{@"Content-Type": @"application/json"}) .withBody(@"["repo-1"]"); ! GHVAPIClient *client = [GHVAPIClient new]; ! // ... ! [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1];
  • 50. HTTP Stubbing Eliminating external dependencies stubRequest(@"GET", @“https://api.github.com/" @“users/modocache/repos") .andReturn(200) .withHeaders(@{@"Content-Type": @"application/json"}) .withBody(@"["repo-1"]"); ! GHVAPIClient *client = [GHVAPIClient new]; ! // ... ! [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1];
  • 51. HTTP Stubbing Eliminating external dependencies stubRequest(@"GET", @“https://api.github.com/" @“users/modocache/repos") .andReturn(200) .withHeaders(@{@"Content-Type": @"application/json"}) .withBody(@"["repo-1"]"); ! GHVAPIClient *client = [GHVAPIClient new]; ! // ... ! [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1];
  • 52. HTTP Stubbing Eliminating external dependencies stubRequest(@"GET", @“https://api.github.com/" @“users/modocache/repos") .andReturn(200) .withHeaders(@{@"Content-Type": @"application/json"}) .withBody(@"["repo-1"]"); ! GHVAPIClient *client = [GHVAPIClient new]; ! // ... ! [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1];
  • 53. HTTP Stubbing Eliminating external dependencies stubRequest(@"GET", @“https://api.github.com/" @“users/modocache/repos") .andReturn(200) .withHeaders(@{@"Content-Type": @"application/json"}) .withBody(@"["repo-1"]"); ! GHVAPIClient *client = [GHVAPIClient new]; ! // ... ! [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1];
  • 54. HTTP Stubbing Eliminating external dependencies stubRequest(@"GET", @“https://api.github.com/" @“users/modocache/repos") .andReturn(200) .withHeaders(@{@"Content-Type": @"application/json"}) .withBody(@"["repo-1"]"); ! GHVAPIClient *client = [GHVAPIClient new]; ! // ... ! [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1];
  • 55. Problems Nocilla Fixes • The test no longer has external dependencies • It’ll pass whether the GitHub API is online or not • It’ll pass even when run offline • The test is fast • It still sends a request, but that request is immediately intercepted and a response is returned
  • 57. Other Nocilla Features • Stub HTTP requests using regular expressions
  • 58. Other Nocilla Features • Stub HTTP requests using regular expressions stubRequest(@"GET", @"https://api.github.com/" @"users/(.*?)/repos".regex)
  • 59. Other Nocilla Features • Stub HTTP requests using regular expressions stubRequest(@"GET", @"https://api.github.com/" @"users/(.*?)/repos".regex) • Return errors, such as for poor internet connection
  • 60. Other Nocilla Features • Stub HTTP requests using regular expressions stubRequest(@"GET", @"https://api.github.com/" @"users/(.*?)/repos".regex) • Return errors, such as for poor internet connection NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:29 userInfo:@{NSLocalizedDescriptionKey: @"Uh-oh!"}]; stubRequest(@"GET", @"...") .andFailWithError(error);
  • 62. Takeaways • Readable, behavior-driven, asynchronous tests with Kiwi • https://github.com/allending/Kiwi
  • 63. Takeaways • Readable, behavior-driven, asynchronous tests with Kiwi • https://github.com/allending/Kiwi pod "Kiwi/XCTest"
  • 64. Takeaways • Readable, behavior-driven, asynchronous tests with Kiwi • https://github.com/allending/Kiwi pod "Kiwi/XCTest" • Eliminate network dependencies with Nocilla • https://github.com/luisobo/Nocilla
  • 65. Takeaways • Readable, behavior-driven, asynchronous tests with Kiwi • https://github.com/allending/Kiwi pod "Kiwi/XCTest" • Eliminate network dependencies with Nocilla • https://github.com/luisobo/Nocilla pod "Nocilla"
  • 67. Questions? @modocache #startup_ios describe(@"this talk", ^{ context(@"after presenting the slides", ^{ it(@"moves to Q&A", ^{ [[you should] askQuestions]; [[you shouldEventually] receive:@selector(stop)]; }); }); });
  • 68. Questions? @modocache #startup_ios describe(@"this talk", ^{ context(@"after presenting the slides", ^{ it(@"moves to Q&A", ^{ [[you should] askQuestions]; [[you shouldEventually] receive:@selector(stop)]; }); }); });
  • 69. Questions? @modocache #startup_ios describe(@"this talk", ^{ context(@"after presenting the slides", ^{ it(@"moves to Q&A", ^{ [[you should] askQuestions]; [[you shouldEventually] receive:@selector(stop)]; }); }); });
  • 70. Questions? @modocache #startup_ios describe(@"this talk", ^{ context(@"after presenting the slides", ^{ it(@"moves to Q&A", ^{ [[you should] askQuestions]; [[you shouldEventually] receive:@selector(stop)]; }); }); });
  • 71. Questions? @modocache #startup_ios describe(@"this talk", ^{ context(@"after presenting the slides", ^{ it(@"moves to Q&A", ^{ [[you should] askQuestions]; [[you shouldEventually] receive:@selector(stop)]; }); }); });