SlideShare une entreprise Scribd logo
1  sur  71
Télécharger pour lire hors ligne
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)];
});
});
});

Contenu connexe

Tendances

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
 

Tendances (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
 

En vedette

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
 

En vedette (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
 

Similaire à 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
 

Similaire à 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
 

Dernier

AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAndrey Devyatkin
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfsudhanshuwaghmare1
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityPrincipled Technologies
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?Antenna Manufacturer Coco
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...DianaGray10
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024The Digital Insurer
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...apidays
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUK Journal
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...Neo4j
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century educationjfdjdjcjdnsjd
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CVKhem
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdflior mazor
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProduct Anonymous
 

Dernier (20)

+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 

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