SlideShare a Scribd company logo
1 of 71
Download to read 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)];
});
});
});

More Related Content

What's hot

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
 

What's hot (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
 

Viewers also liked

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
 

Viewers also liked (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
 

Similar to 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
 

Similar to 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
 

Recently uploaded

Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024The Digital Insurer
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxKatpro Technologies
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsRoshan Dwivedi
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Igalia
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024Results
 
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
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Servicegiselly40
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Scriptwesley chun
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - 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
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 
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
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024The Digital Insurer
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
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
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreternaman860154
 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...gurkirankumar98700
 

Recently uploaded (20)

Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 
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 ...
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - 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
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
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...
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
 

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