SlideShare a Scribd company logo
1 of 108
@YelpEngineering
YelpEngineers
engineeringblog.yelp.com
github.com/yelpyelp.com/careers
Building Yelp for Apple Watch
Bill Meltsner
wmeltsner@yelp.com
@billmeltsner
Today
Initial Scoping / Planning
Yelp.app on Apple Watch Deep Dive
Lessons Learned
Who I Am
iOS Technical Lead
Yelp on Apple Watch project lead
Worked largely on Watch app logic
Who We Were
Designer Engineer Product Manager Engineer (Intern)
Why Build Yelp Watch App?
Yelp everywhere
Day-one advantage
Knew we could build a great experience
Initial Scoping
time = features
Initial Scoping
unlimited time* = unlimited features
*this never happens
Initial Scoping
finite time = finite features
Initial Scoping
Product team defined features
Engineering team estimated available time
Worked together to define MVP as cost of time
versus feature
Initial Scoping
UI & Logic could be parallelized
- 1 engineer for each
Milestones for MVP, MVP+1, MVP+2, …
Flexibility in our schedule to add / remove
features and stay agile
Demo
Yelp Watch App
Technical Overview
WatchKit - A Changing Landscape
Brand new platform with docs & APIs changing
drastically between betas
No defined best practices
Focused on making best-effort technical
decisions with the ability to refine later
Overview of a WatchKit app
Parent App
API Requests
Watch App
Storyboard
WatchKit Extension
Location
Logic
Interface
Control
Images
iPhone Apple Watch
Interface Controllers
View Controller analog
Interface hierarchy is fixed
YPWKSearchResultsInterfaceController
Storyboard Overview
Before After
Networking
API requests owned by parent app
Image loading owned by extension
Location
Owned by extension – runs in foreground,
parent app runs in background
We only request foreground access
Location
Permissions belong to parent
app, must be granted on phone
Phone ↔ Watch Communications
All calls in one iteration of the run loop
coalesced together
Communication between watch and phone is
rate-limited serial queue
Overhead is high – batch your calls!
Images
Key part of our search UI
Naive approach: send each image to the watch
as it’s loaded
Result: traffic jam of communications,
unresponsive app
Images
Solution: Wait x seconds, send all images
loaded in that timeframe at once
Problem: that can be a lot of data
Solution Part 2: crush the heck out of ‘em
UIImageJPEGRepresentation(image, 0.0) // max compression
Lessons Learned
Think like a Startup
Priority 1 was being there on launch day
MVP comes first, everything else can wait
Technical debt is not inherently bad
Plan Ahead
Define designs and scope before writing any
code
New platforms are hard to predict effectively
Bend but don’t break
Questions?
Testing The Yelp App
iOS & Android
Who We Are
Mason Glidden
● iOS Engineer
● iOS Testing
o KIF & Jenkins
● mglidden@yelp.com
Tim Mellor
● Android Engineer
● Android Testing
o Espresso & Jenkins
How we develop new mobile APIs
iOS & Android
- Tests & Testing Strategy
Today
Building New Mobile APIs
How we use our documentation to test new APIs
Mobile APIs @ Yelp
● API shared by iOS & Android
● New APIs start with documentation and examples
● Client and API can be developed simultaneously
● API team manages backwards compatibility tests
/*
h2. Photo (full)
|_. Name |_. Type |_. Description |
| id | string | Identifier |
| time_created | time | Timestamp for when photo was
uploaded |
| url_prefix | string | Prefix for image url |
| user_passport |
"Passport":{{site.url}}/v1/objects/passport/index.html |
Passport for user who uploaded photo (not provided for
photos added by biz owners to their own biz from their biz
owner account) (Optional) |
| caption | string | Caption |
| feedback_positive_count | integer | Number of likes for
the photo |
|
*/
{
"id": "PHOTO_ID123",
"time_created": 1370985832,
"user_passport": {% include_jsondoc
"v1/objects/passport/default.json" Passport %},
"url_prefix": "http://s3-
media4.ak.yelpcdn.com/bphoto/_Xwa-lWTpivnlB1tgX-sJw/",
"caption": "Korean tacos ($5.75)",
"feedback_positive_count": 19,
}
Documentation
/*
h2. Photo (full)
|_. Name |_. Type |_. Description |
| id | string | Identifier |
| time_created | time | Timestamp for when photo was
uploaded |
| url_prefix | string | Prefix for image url |
| user_passport |
"Passport":{{site.url}}/v1/objects/passport/index.html |
Passport for user who uploaded photo (not provided for
photos added by biz owners to their own biz from their biz
owner account) (Optional) |
| caption | string | Caption |
| feedback_positive_count | integer | Number of likes for
the photo |
|
*/
{
"id": "PHOTO_ID123",
"time_created": 1370985832,
"user_passport": {% include_jsondoc
"v1/objects/passport/default.json" Passport %},
"url_prefix": "http://s3-
media4.ak.yelpcdn.com/bphoto/_Xwa-lWTpivnlB1tgX-sJw/",
"caption": "Korean tacos ($5.75)",
"feedback_positive_count": 19,
}
Documentation
Textile
/*
h2. Photo (full)
|_. Name |_. Type |_. Description |
| id | string | Identifier |
| time_created | time | Timestamp for when photo was
uploaded |
| url_prefix | string | Prefix for image url |
| user_passport |
"Passport":{{site.url}}/v1/objects/passport/index.html |
Passport for user who uploaded photo (not provided for
photos added by biz owners to their own biz from their biz
owner account) (Optional) |
| caption | string | Caption |
| feedback_positive_count | integer | Number of likes for
the photo |
|
*/
{
"id": "PHOTO_ID123",
"time_created": 1370985832,
"user_passport": {% include_jsondoc
"v1/objects/passport/default.json" Passport %},
"url_prefix": "http://s3-
media4.ak.yelpcdn.com/bphoto/_Xwa-lWTpivnlB1tgX-sJw/",
"caption": "Korean tacos ($5.75)",
"feedback_positive_count": 19,
}
Documentation
JSONDoc
Textile
Documentation
/*
h2. Photo (full)
|_. Name |_. Type |_. Description |
| id | string | Identifier |
| time_created | time | Timestamp for when photo was
uploaded |
| url_prefix | string | Prefix for image url |
| user_passport |
"Passport":{{site.url}}/v1/objects/passport/index.html |
Passport for user who uploaded photo (not provided for
photos added by biz owners to their own biz from their biz
owner account) (Optional) |
| caption | string | Caption |
| feedback_positive_count | integer | Number of likes for
the photo |
|
*/
{
"id": "PHOTO_ID123",
"time_created": 1370985832,
"user_passport": {% include_jsondoc
"v1/objects/passport/default.json" Passport %},
"url_prefix": "http://s3-
media4.ak.yelpcdn.com/bphoto/_Xwa-lWTpivnlB1tgX-sJw/",
"caption": "Korean tacos ($5.75)",
"feedback_positive_count": 19,
}
Documentation -> JSON
● Included as submodule in client repos
● Build step to flatten documentation into JSON (e.g. v1-
-objects--photo+full.json)
● Code requests specific mocks
Why This Approach Works for Us
● API & client contract
● Fewer dependencies for developers & Jenkins
● Improved test speed & reliability on iOS & Android
iOS Testing @ Yelp
Mason Glidden
● Prevent Regressions
● Give developers confidence
● Run quickly
● Reliable results
● Easy to write
Test Goals
● Unit Tests
● Integration Tests
● Acceptance Tests
Test Types
● Prevent UI & Logic Regressions
● ~150 logic unit tests
● ~100 network request contract tests
● ~650 view tests
● Continuous Integration on Jenkins
Unit Tests
● Generally pretty simple
● Test-Driven Development
● Super fast to run
Logic Tests
Example: Business Hours Logic
- (void)testOpensSoon {
// Test that "Opens soon" appears with the correct time interval
[NSDate yk_setDate:[NSDate
dateWithTimeIntervalSince1970:1356040364]];
NSArray *openHoursArray = @[@[@5160, @5460]];
OpenHours *openHours = [OpenHours openHoursFromJSON:openHoursArray
timeZoneString:@"America/Los_Angeles"];
STAssertEqualObjects(@"Opens in 8 min",
[openHours openOrClosedStringUsingMinutes:YES], nil);
}
Logic Tests
● Makes sure client can still parse documented API changes
● Example: ReviewsListRequestTest
- (void)testList {
ReviewsListRequest *request = [[ReviewsListRequest alloc] init];
[OHHTTPStubs yp_receiveFromPath:@"v1--reviews+reviews.json"
statusCode:200 MIMEType:@"application/json" afterDelay:0.1];
[request listWithBusinessId:@"BIZID" selectedReviewId:nil offset:0
limit:10 delegate:self];
[self waitForStatus:YPAsyncTestWaitStatusSuccess timeout:10.0
requestToCancelOnTimeout:request];
}
Parsing Tests
View Tests
● View with mock data
● Screenshot of view
● Compares with
previous versions
● Based off GHUnit
View Tests
View Tests
● Example: contribution buttons view test
- (void)testBasicButtons {
Business *business = [Business businessFromJSONDictionary:
[YPDebug JSONFromResource:@"v1--objects--business+full.json"]
request:nil context:nil];
YPBusinessContributeButtons *buttons =
[[YPBusinessContributeButtons alloc] init];
[buttons setBusiness:business];
YPVerifyView(buttons);
}
View Tests
● Pros:
○ Easy way to catch regressions
○ Invaluable when refactoring or updating to new OS
versions
● Cons:
○ Slow: ~¾ seconds per test
○ Lots of false-positive failures
Integration Tests
● Testing that application behaves as expected
● Interaction between view controllers
● Primary signals of a problem:
○ Non-visual - analytics & network requests
○ Visual - button or label
● ~225 Integration tests
KIF
● ~150 KIF tests
● Uses accessibility labels to navigate
● Custom hooks for analytics, requests
● Continuous integration on Jenkins
● Separate iPad and iPhone tests
github.com/kif-framework/KIF
Integration Test Example
Integration Test Example
● Example: ReviewCompositionIntegrationTest
- (void)testReviewWrite {
[YPAPI yp_addMockSessionConfirmed:YES sendNotification:YES];
[self yp_openBusinessViewController];
[tester tapViewWithAccessibilityLabel:@"Write a Review"];
[tester yp_waitForModalViewControllerOfClass:[ReviewComposeViewController class]];
[tester yp_waitForExpectedAnalytics:@[@{@"iri": kAnalyticsReviewWriteIRI,
@"params": @{@"intended_compose_type": @"add"}]];
[tester tapViewWithAccessibilityLabel:@"Rating"];
[tester clearTextFromAndThenEnterText:@"My review"
intoViewWithAccessibilityLabel:@"Write your review here."];
[tester tapViewWithAccessibilityLabel:@"Post"];
[tester yp_waitForRequestWithPath:@"/review/save" queryParams:nil
postParams:@{ @"text": @"My review", @"rating": @"3" }];
}
Integration Test Example
● Example: ReviewCompositionIntegrationTest
- (void)testReviewWrite {
[YPAPI yp_addMockSessionConfirmed:YES sendNotification:YES];
[self yp_openBusinessViewController];
[tester tapViewWithAccessibilityLabel:@"Write a Review"];
[tester yp_waitForModalViewControllerOfClass:[ReviewComposeViewController class]];
[tester yp_waitForExpectedAnalytics:@[@{@"iri": kAnalyticsReviewWriteIRI,
@"params": @{@"intended_compose_type": @"add"}]];
[tester tapViewWithAccessibilityLabel:@"Rating"];
[tester clearTextFromAndThenEnterText:@"My review"
intoViewWithAccessibilityLabel:@"Write your review here."];
[tester tapViewWithAccessibilityLabel:@"Post"];
[tester yp_waitForRequestWithPath:@"/review/save" queryParams:nil
postParams:@{ @"text": @"My review", @"rating": @"3" }];
}
Integration Test Example
● Example: ReviewCompositionIntegrationTest
- (void)testReviewWrite {
[YPAPI yp_addMockSessionConfirmed:YES sendNotification:YES];
[self yp_openBusinessViewController];
[tester tapViewWithAccessibilityLabel:@"Write a Review"];
[tester yp_waitForModalViewControllerOfClass:[ReviewComposeViewController class]];
[tester yp_waitForExpectedAnalytics:@[@{@"iri": kAnalyticsReviewWriteIRI,
@"params": @{@"intended_compose_type": @"add"}]];
[tester tapViewWithAccessibilityLabel:@"Rating"];
[tester clearTextFromAndThenEnterText:@"My review"
intoViewWithAccessibilityLabel:@"Write your review here."];
[tester tapViewWithAccessibilityLabel:@"Post"];
[tester yp_waitForRequestWithPath:@"/review/save" queryParams:nil
postParams:@{ @"text": @"My review", @"rating": @"3" }];
}
Integration Test Example
● Example: ReviewCompositionIntegrationTest
- (void)testReviewWrite {
[YPAPI yp_addMockSessionConfirmed:YES sendNotification:YES];
[self yp_openBusinessViewController];
[tester tapViewWithAccessibilityLabel:@"Write a Review"];
[tester yp_waitForModalViewControllerOfClass:[ReviewComposeViewController class]];
[tester yp_waitForExpectedAnalytics:@[@{@"iri": kAnalyticsReviewWriteIRI,
@"params": @{@"intended_compose_type": @"add"}]];
[tester tapViewWithAccessibilityLabel:@"Rating"];
[tester clearTextFromAndThenEnterText:@"My review"
intoViewWithAccessibilityLabel:@"Write your review here."];
[tester tapViewWithAccessibilityLabel:@"Post"];
[tester yp_waitForRequestWithPath:@"/review/save" queryParams:nil
postParams:@{ @"text": @"My review", @"rating": @"3" }];
}
Sandboxing
Mocked During Tests:
● Networking
● Date, Time, Timezone
● Device permissions
● Singletons
Between Test Runs:
● Clean caches
● Reset user defaults
● Reset navigation stack
● Device orientation
Other Tooling
● OHHTTPStubs to block &
mock network requests
● OCMock for mocking
● XCTool to run our tests
Acceptance Tests
● Test overall look and feel
● ~50 manual test cases
○ Moving some to KIF
● iOS7 & 8, iPad & iPhone
● Run by Engineers + PM during release process
Closing Thoughts
● API mocks make it easy for us to reliably grow our
testing suite
● Different types of tests for different problems
● Sandboxes to create consistent environments
● KIF <3
Android Testing @ Yelp
Tim Mellor
tfmellor@yelp.com
tests = tools + code
Simple, right?
● AndroidTestCase
● InstrumentationTestCase
● ApplicationTestCase
● ActivityTestCase
● ActivityUnitTestCase
● ActivityInstrumentationTestCase
● ActivityInstrumentationTestCase2
More decisions
Problem: Devices
● Devices are
necessary
● Devices suck
● Virtual devices are
bearable
Solution: Devices
● Genymotion’s gmtool
● Clone image into new device
● Speed of Genymotion
$ python gmtool_wrapper.py start 
--vms '{"18":1, "19":1, "21":1}'
Problem: Flakes!
● Part 1: Instrumentation + Device
● Part 2: Test library
Android Instrumentation
Instrumentation consequences
● Uncaught exceptions halt test suite
● Activities/Services/etc. stay open
Solution: Instrumentation Flakes
● One test per instrumentation run!
$ adb shell pm clear com.yelp.droid
Flakiness and Test libraries
Robotium and its
solo.waitFor* methods
Android test kit to the rescue!
Main Thread
click()
Espresso
blocked
Main Thread
Test thread
assertions
Test
click()
Espresso
blocked
Main Thread
Test thread
assertions
Test
Task Thread
Background task
Problem: slow test suites
● Consequence of needing devices
● Long = impractical
Solution: test sharding
$ adb shell am instrument -w 
-e numShards 4 
-e shardIndex 1
● github.com/shazam/fork
● Resources are the limit!
Yelp Testing Process
● ~300 unit tests
● ~100 integration tests
● ~150 UI integration tests
● Manual testing against production
● Beta group
● 50% roll-out in Play Store
UI Integration test toolkit @ Yelp
● Espresso!
● Home-rolled MockHttpClient
o MockResponse
o MockRequestMatcher
● Spoon
public void test_ClickingBookmark_SendsAddRequestAndUpdatesUi() {
mBusiness.setBookmarked(false);
setActivityIntent(ActivityBusinessPage.intentForBusiness(getYelpContext(), mBusiness));
getActivity();
takeScreenshot("business detail unbookmarked");
// Check that the bookmark button has the text “Bookmark” and click on it
// This should trigger a bookmark add request.
onView(withId(R.id.bookmark))
.check(matches(allOf(isDisplayed(), withText(R.string.action_bookmark))))
.perform(click());
takeScreenshot("bookmarked");
// Make sure we hit bookmarks/add with the proper params.
assertThat(mAddBookmarkResponse.getRequestCount(), is(1));
// Make sure the "Bookmarked" button now shows.
onView(withId(R.id.bookmark))
.check(matches(allOf(isDisplayed(), withText(R.string.bookmarked))));
}
public void test_ClickingBookmark_SendsAddRequestAndUpdatesUi() {
mBusiness.setBookmarked(false);
setActivityIntent(ActivityBusinessPage.intentForBusiness(getYelpContext(), mBusiness));
getActivity();
takeScreenshot("business detail unbookmarked");
// Check that the bookmark button has the text “Bookmark” and click on it
// This should trigger a bookmark add request.
onView(withId(R.id.bookmark))
.check(matches(allOf(isDisplayed(), withText(R.string.action_bookmark))))
.perform(click());
takeScreenshot("bookmarked");
// Make sure we hit bookmarks/add with the proper params.
assertThat(mAddBookmarkResponse.getRequestCount(), is(1));
// Make sure the "Bookmarked" button now shows.
onView(withId(R.id.bookmark))
.check(matches(allOf(isDisplayed(), withText(R.string.bookmarked))));
}
public void test_ClickingBookmark_SendsAddRequestAndUpdatesUi() {
mBusiness.setBookmarked(false);
setActivityIntent(ActivityBusinessPage.intentForBusiness(getYelpContext(), mBusiness));
getActivity();
takeScreenshot("business detail unbookmarked");
// Check that the bookmark button has the text “Bookmark” and click on it
// This should trigger a bookmark add request.
onView(withId(R.id.bookmark))
.check(matches(allOf(isDisplayed(), withText(R.string.action_bookmark))))
.perform(click());
takeScreenshot("bookmarked");
// Make sure we hit bookmarks/add with the proper params.
assertThat(mAddBookmarkResponse.getRequestCount(), is(1));
// Make sure the "Bookmarked" button now shows.
onView(withId(R.id.bookmark))
.check(matches(allOf(isDisplayed(), withText(R.string.bookmarked))));
}
public void test_ClickingBookmark_SendsAddRequestAndUpdatesUi() {
mBusiness.setBookmarked(false);
setActivityIntent(ActivityBusinessPage.intentForBusiness(getYelpContext(), mBusiness));
getActivity();
takeScreenshot("business detail unbookmarked");
// Check that the bookmark button has the text “Bookmark” and click on it
// This should trigger a bookmark add request.
onView(withId(R.id.bookmark))
.check(matches(allOf(isDisplayed(), withText(R.string.action_bookmark))))
.perform(click());
takeScreenshot("bookmarked");
// Make sure we hit bookmarks/add with the proper params.
assertThat(mAddBookmarkResponse.getRequestCount(), is(1));
// Make sure the "Bookmarked" button now shows.
onView(withId(R.id.bookmark))
.check(matches(allOf(isDisplayed(), withText(R.string.bookmarked))));
}
public void test_WriteTip_SendsProperRequest() {
setMockSession();
MockResponse tipSaveResponse = mMockHttpClient.addDefaultMock(
ApiPath.QUICKTIPS_SAVE, getTipSaveParams());
openTipPage();
onView(withId(R.id.edit_text)).perform(typeText(TIP_TEXT));
takeScreenshot("tip typed");
onView(withId(R.id.done_button)).perform(click());
takeScreenshot("business page");
// We should show a "Thanks for the tip!" dialog on the business page.
onView(withText(R.string.thanks_for_the_tip)).check(matches(isDisplayed()));
assertThat(tipSaveResponse.getRequestCount(), is(1));
}
public void test_WriteTip_SendsProperRequest() {
setMockSession();
MockResponse tipSaveResponse = mMockHttpClient.addDefaultMock(
ApiPath.QUICKTIPS_SAVE, getTipSaveParams());
openTipPage();
onView(withId(R.id.edit_text)).perform(typeText(TIP_TEXT));
takeScreenshot("tip typed");
onView(withId(R.id.done_button)).perform(click());
takeScreenshot("business page");
// We should show a "Thanks for the tip!" dialog on the business page.
onView(withText(R.string.thanks_for_the_tip)).check(matches(isDisplayed()));
assertThat(tipSaveResponse.getRequestCount(), is(1));
}
public void test_WriteTip_SendsProperRequest() {
setMockSession();
MockResponse tipSaveResponse = mMockHttpClient.addDefaultMock(
ApiPath.QUICKTIPS_SAVE, getTipSaveParams());
openTipPage();
onView(withId(R.id.edit_text)).perform(typeText(TIP_TEXT));
takeScreenshot("tip typed");
onView(withId(R.id.done_button)).perform(click());
takeScreenshot("business page");
// We should show a "Thanks for the tip!" dialog on the business page.
onView(withText(R.string.thanks_for_the_tip)).check(matches(isDisplayed()));
assertThat(tipSaveResponse.getRequestCount(), is(1));
}
public void test_WriteTip_SendsProperRequest() {
setMockSession();
MockResponse tipSaveResponse = mMockHttpClient.addDefaultMock(
ApiPath.QUICKTIPS_SAVE, getTipSaveParams());
openTipPage();
onView(withId(R.id.edit_text)).perform(typeText(TIP_TEXT));
takeScreenshot("tip typed");
onView(withId(R.id.done_button)).perform(click());
takeScreenshot("business page");
// We should show a "Thanks for the tip!" dialog on the business page.
onView(withText(R.string.thanks_for_the_tip)).check(matches(isDisplayed()));
assertThat(tipSaveResponse.getRequestCount(), is(1));
}
● Library choices matter
● Address the issues at the source!
● Tests don’t have to be a pain
Lessons learned
Questions?

More Related Content

What's hot

WinOps Conf 2016 - Gael Colas - Configuration Management Theory: Why Idempote...
WinOps Conf 2016 - Gael Colas - Configuration Management Theory: Why Idempote...WinOps Conf 2016 - Gael Colas - Configuration Management Theory: Why Idempote...
WinOps Conf 2016 - Gael Colas - Configuration Management Theory: Why Idempote...WinOps Conf
 
Docker/DevOps Meetup: Metrics-Driven Continuous Performance and Scalabilty
Docker/DevOps Meetup: Metrics-Driven Continuous Performance and ScalabiltyDocker/DevOps Meetup: Metrics-Driven Continuous Performance and Scalabilty
Docker/DevOps Meetup: Metrics-Driven Continuous Performance and ScalabiltyAndreas Grabner
 
BTD2015 - Your Place In DevTOps is Finding Solutions - Not Just Bugs!
BTD2015 - Your Place In DevTOps is Finding Solutions - Not Just Bugs!BTD2015 - Your Place In DevTOps is Finding Solutions - Not Just Bugs!
BTD2015 - Your Place In DevTOps is Finding Solutions - Not Just Bugs!Andreas Grabner
 
JavaOne - Performance Focused DevOps to Improve Cont Delivery
JavaOne - Performance Focused DevOps to Improve Cont DeliveryJavaOne - Performance Focused DevOps to Improve Cont Delivery
JavaOne - Performance Focused DevOps to Improve Cont DeliveryAndreas Grabner
 
StarWest 2013 Performance is not an afterthought – make it a part of your Agi...
StarWest 2013 Performance is not an afterthought – make it a part of your Agi...StarWest 2013 Performance is not an afterthought – make it a part of your Agi...
StarWest 2013 Performance is not an afterthought – make it a part of your Agi...Andreas Grabner
 
Performance Quality Metrics for Mobile Web and Mobile Native - Agile Testing ...
Performance Quality Metrics for Mobile Web and Mobile Native - Agile Testing ...Performance Quality Metrics for Mobile Web and Mobile Native - Agile Testing ...
Performance Quality Metrics for Mobile Web and Mobile Native - Agile Testing ...Andreas Grabner
 
Fraud Engineering, from Merchant Risk Council Annual Meeting 2012
Fraud Engineering, from Merchant Risk Council Annual Meeting 2012Fraud Engineering, from Merchant Risk Council Annual Meeting 2012
Fraud Engineering, from Merchant Risk Council Annual Meeting 2012Nick Galbreath
 
Tis The Season: Load Testing Tips and Checklist for Retail Seasonal Readiness
Tis The Season: Load Testing Tips and Checklist for Retail Seasonal ReadinessTis The Season: Load Testing Tips and Checklist for Retail Seasonal Readiness
Tis The Season: Load Testing Tips and Checklist for Retail Seasonal ReadinessSOASTA
 
London web perfug_performancefocused_devops_feb2014
London web perfug_performancefocused_devops_feb2014London web perfug_performancefocused_devops_feb2014
London web perfug_performancefocused_devops_feb2014Andreas Grabner
 
Hugs instead of Bugs: Dreaming of Quality Tools for Devs and Testers
Hugs instead of Bugs: Dreaming of Quality Tools for Devs and TestersHugs instead of Bugs: Dreaming of Quality Tools for Devs and Testers
Hugs instead of Bugs: Dreaming of Quality Tools for Devs and TestersAndreas Grabner
 
Shawn Wallace - Test automation in brownfield applications
Shawn Wallace - Test automation in brownfield applicationsShawn Wallace - Test automation in brownfield applications
Shawn Wallace - Test automation in brownfield applicationsQA or the Highway
 
Software testing presentation
Software testing presentationSoftware testing presentation
Software testing presentationNikolas Vourlakis
 
Living with acceptance tests: Beyond Write-Once (XP NYC)
Living with acceptance tests: Beyond Write-Once (XP NYC)Living with acceptance tests: Beyond Write-Once (XP NYC)
Living with acceptance tests: Beyond Write-Once (XP NYC)Daniel Wellman
 
WE are Doing it Wrong - Dmitry Sharkov
WE are Doing it Wrong - Dmitry SharkovWE are Doing it Wrong - Dmitry Sharkov
WE are Doing it Wrong - Dmitry SharkovQA or the Highway
 
An introduction to Reactive applications, Reactive Streams, and options for t...
An introduction to Reactive applications, Reactive Streams, and options for t...An introduction to Reactive applications, Reactive Streams, and options for t...
An introduction to Reactive applications, Reactive Streams, and options for t...Steve Pember
 
Designing Self-maintaining UI Tests for Web Applications
Designing Self-maintaining UI Tests for Web ApplicationsDesigning Self-maintaining UI Tests for Web Applications
Designing Self-maintaining UI Tests for Web ApplicationsTechWell
 
AB Testing at Expedia
AB Testing at ExpediaAB Testing at Expedia
AB Testing at ExpediaPaul Lucas
 
I Don't Test Often ...
I Don't Test Often ...I Don't Test Often ...
I Don't Test Often ...Gareth Bowles
 
Behavior Driven Development - TdT@Cluj #15
Behavior Driven Development - TdT@Cluj #15Behavior Driven Development - TdT@Cluj #15
Behavior Driven Development - TdT@Cluj #15Tabăra de Testare
 

What's hot (20)

WinOps Conf 2016 - Gael Colas - Configuration Management Theory: Why Idempote...
WinOps Conf 2016 - Gael Colas - Configuration Management Theory: Why Idempote...WinOps Conf 2016 - Gael Colas - Configuration Management Theory: Why Idempote...
WinOps Conf 2016 - Gael Colas - Configuration Management Theory: Why Idempote...
 
Docker/DevOps Meetup: Metrics-Driven Continuous Performance and Scalabilty
Docker/DevOps Meetup: Metrics-Driven Continuous Performance and ScalabiltyDocker/DevOps Meetup: Metrics-Driven Continuous Performance and Scalabilty
Docker/DevOps Meetup: Metrics-Driven Continuous Performance and Scalabilty
 
BTD2015 - Your Place In DevTOps is Finding Solutions - Not Just Bugs!
BTD2015 - Your Place In DevTOps is Finding Solutions - Not Just Bugs!BTD2015 - Your Place In DevTOps is Finding Solutions - Not Just Bugs!
BTD2015 - Your Place In DevTOps is Finding Solutions - Not Just Bugs!
 
JavaOne - Performance Focused DevOps to Improve Cont Delivery
JavaOne - Performance Focused DevOps to Improve Cont DeliveryJavaOne - Performance Focused DevOps to Improve Cont Delivery
JavaOne - Performance Focused DevOps to Improve Cont Delivery
 
StarWest 2013 Performance is not an afterthought – make it a part of your Agi...
StarWest 2013 Performance is not an afterthought – make it a part of your Agi...StarWest 2013 Performance is not an afterthought – make it a part of your Agi...
StarWest 2013 Performance is not an afterthought – make it a part of your Agi...
 
Performance Quality Metrics for Mobile Web and Mobile Native - Agile Testing ...
Performance Quality Metrics for Mobile Web and Mobile Native - Agile Testing ...Performance Quality Metrics for Mobile Web and Mobile Native - Agile Testing ...
Performance Quality Metrics for Mobile Web and Mobile Native - Agile Testing ...
 
Fraud Engineering, from Merchant Risk Council Annual Meeting 2012
Fraud Engineering, from Merchant Risk Council Annual Meeting 2012Fraud Engineering, from Merchant Risk Council Annual Meeting 2012
Fraud Engineering, from Merchant Risk Council Annual Meeting 2012
 
Tis The Season: Load Testing Tips and Checklist for Retail Seasonal Readiness
Tis The Season: Load Testing Tips and Checklist for Retail Seasonal ReadinessTis The Season: Load Testing Tips and Checklist for Retail Seasonal Readiness
Tis The Season: Load Testing Tips and Checklist for Retail Seasonal Readiness
 
London web perfug_performancefocused_devops_feb2014
London web perfug_performancefocused_devops_feb2014London web perfug_performancefocused_devops_feb2014
London web perfug_performancefocused_devops_feb2014
 
Hugs instead of Bugs: Dreaming of Quality Tools for Devs and Testers
Hugs instead of Bugs: Dreaming of Quality Tools for Devs and TestersHugs instead of Bugs: Dreaming of Quality Tools for Devs and Testers
Hugs instead of Bugs: Dreaming of Quality Tools for Devs and Testers
 
Shawn Wallace - Test automation in brownfield applications
Shawn Wallace - Test automation in brownfield applicationsShawn Wallace - Test automation in brownfield applications
Shawn Wallace - Test automation in brownfield applications
 
Software testing presentation
Software testing presentationSoftware testing presentation
Software testing presentation
 
Living with acceptance tests: Beyond Write-Once (XP NYC)
Living with acceptance tests: Beyond Write-Once (XP NYC)Living with acceptance tests: Beyond Write-Once (XP NYC)
Living with acceptance tests: Beyond Write-Once (XP NYC)
 
WE are Doing it Wrong - Dmitry Sharkov
WE are Doing it Wrong - Dmitry SharkovWE are Doing it Wrong - Dmitry Sharkov
WE are Doing it Wrong - Dmitry Sharkov
 
An introduction to Reactive applications, Reactive Streams, and options for t...
An introduction to Reactive applications, Reactive Streams, and options for t...An introduction to Reactive applications, Reactive Streams, and options for t...
An introduction to Reactive applications, Reactive Streams, and options for t...
 
Designing Self-maintaining UI Tests for Web Applications
Designing Self-maintaining UI Tests for Web ApplicationsDesigning Self-maintaining UI Tests for Web Applications
Designing Self-maintaining UI Tests for Web Applications
 
AB Testing at Expedia
AB Testing at ExpediaAB Testing at Expedia
AB Testing at Expedia
 
I Don't Test Often ...
I Don't Test Often ...I Don't Test Often ...
I Don't Test Often ...
 
Behavior Driven Development - TdT@Cluj #15
Behavior Driven Development - TdT@Cluj #15Behavior Driven Development - TdT@Cluj #15
Behavior Driven Development - TdT@Cluj #15
 
Software testing
Software testingSoftware testing
Software testing
 

Similar to Yelp Tech Talks: Mobile Testing 1, 2, 3

How to feature flag and run experiments in iOS and Android
How to feature flag and run experiments in iOS and AndroidHow to feature flag and run experiments in iOS and Android
How to feature flag and run experiments in iOS and AndroidOptimizely
 
Android UI Testing with Appium
Android UI Testing with AppiumAndroid UI Testing with Appium
Android UI Testing with AppiumLuke Maung
 
Node in Production at Aviary
Node in Production at AviaryNode in Production at Aviary
Node in Production at AviaryAviary
 
Use Jenkins For Continuous Load Testing And Mobile Test Automation
Use Jenkins For Continuous Load Testing And Mobile Test AutomationUse Jenkins For Continuous Load Testing And Mobile Test Automation
Use Jenkins For Continuous Load Testing And Mobile Test AutomationClever Moe
 
Vipin qa engineer-3.5+years_exp
Vipin qa engineer-3.5+years_expVipin qa engineer-3.5+years_exp
Vipin qa engineer-3.5+years_expVipin Gupta
 
Building a scalable app factory with Appcelerator Platform
Building a scalable app factory with Appcelerator PlatformBuilding a scalable app factory with Appcelerator Platform
Building a scalable app factory with Appcelerator PlatformAngus Fox
 
Java script unit testing
Java script unit testingJava script unit testing
Java script unit testingMats Bryntse
 
Appium workshop technopark trivandrum
Appium workshop technopark trivandrumAppium workshop technopark trivandrum
Appium workshop technopark trivandrumSyam Sasi
 
Closer To the Metal - Why and How We Use XCTest and Espresso by Mario Negro P...
Closer To the Metal - Why and How We Use XCTest and Espresso by Mario Negro P...Closer To the Metal - Why and How We Use XCTest and Espresso by Mario Negro P...
Closer To the Metal - Why and How We Use XCTest and Espresso by Mario Negro P...Sauce Labs
 
Building Creative Product Extensions with Experience Manager
Building Creative Product Extensions with Experience ManagerBuilding Creative Product Extensions with Experience Manager
Building Creative Product Extensions with Experience Managerconnectwebex
 
Appium, Test-Driven Development, and Continuous Integration
Appium, Test-Driven Development, and Continuous IntegrationAppium, Test-Driven Development, and Continuous Integration
Appium, Test-Driven Development, and Continuous IntegrationTechWell
 
From MEAN to the MERN Stack
From MEAN to the MERN StackFrom MEAN to the MERN Stack
From MEAN to the MERN StackTroy Miles
 
Do You Enjoy Espresso in Android App Testing?
Do You Enjoy Espresso in Android App Testing?Do You Enjoy Espresso in Android App Testing?
Do You Enjoy Espresso in Android App Testing?Bitbar
 
ITB2015 - Crash Course in Ionic + AngularJS
ITB2015 - Crash Course in Ionic + AngularJSITB2015 - Crash Course in Ionic + AngularJS
ITB2015 - Crash Course in Ionic + AngularJSOrtus Solutions, Corp
 
Crash Course in AngularJS + Ionic (Deep dive)
Crash Course in AngularJS + Ionic (Deep dive)Crash Course in AngularJS + Ionic (Deep dive)
Crash Course in AngularJS + Ionic (Deep dive)ColdFusionConference
 
Continuous Integration, Deploy, Test From Beginning To End 2014
Continuous Integration, Deploy, Test From Beginning To End 2014Continuous Integration, Deploy, Test From Beginning To End 2014
Continuous Integration, Deploy, Test From Beginning To End 2014Clever Moe
 

Similar to Yelp Tech Talks: Mobile Testing 1, 2, 3 (20)

How to feature flag and run experiments in iOS and Android
How to feature flag and run experiments in iOS and AndroidHow to feature flag and run experiments in iOS and Android
How to feature flag and run experiments in iOS and Android
 
Android UI Testing with Appium
Android UI Testing with AppiumAndroid UI Testing with Appium
Android UI Testing with Appium
 
Node in Production at Aviary
Node in Production at AviaryNode in Production at Aviary
Node in Production at Aviary
 
Use Jenkins For Continuous Load Testing And Mobile Test Automation
Use Jenkins For Continuous Load Testing And Mobile Test AutomationUse Jenkins For Continuous Load Testing And Mobile Test Automation
Use Jenkins For Continuous Load Testing And Mobile Test Automation
 
Vipin qa engineer-3.5+years_exp
Vipin qa engineer-3.5+years_expVipin qa engineer-3.5+years_exp
Vipin qa engineer-3.5+years_exp
 
Building a scalable app factory with Appcelerator Platform
Building a scalable app factory with Appcelerator PlatformBuilding a scalable app factory with Appcelerator Platform
Building a scalable app factory with Appcelerator Platform
 
Java script unit testing
Java script unit testingJava script unit testing
Java script unit testing
 
Appium workshop technopark trivandrum
Appium workshop technopark trivandrumAppium workshop technopark trivandrum
Appium workshop technopark trivandrum
 
Closer To the Metal - Why and How We Use XCTest and Espresso by Mario Negro P...
Closer To the Metal - Why and How We Use XCTest and Espresso by Mario Negro P...Closer To the Metal - Why and How We Use XCTest and Espresso by Mario Negro P...
Closer To the Metal - Why and How We Use XCTest and Espresso by Mario Negro P...
 
Nativescript with angular 2
Nativescript with angular 2Nativescript with angular 2
Nativescript with angular 2
 
ATAGTR2017 Appium
ATAGTR2017 AppiumATAGTR2017 Appium
ATAGTR2017 Appium
 
Building Creative Product Extensions with Experience Manager
Building Creative Product Extensions with Experience ManagerBuilding Creative Product Extensions with Experience Manager
Building Creative Product Extensions with Experience Manager
 
Appium, Test-Driven Development, and Continuous Integration
Appium, Test-Driven Development, and Continuous IntegrationAppium, Test-Driven Development, and Continuous Integration
Appium, Test-Driven Development, and Continuous Integration
 
From MEAN to the MERN Stack
From MEAN to the MERN StackFrom MEAN to the MERN Stack
From MEAN to the MERN Stack
 
Intro to appcelerator
Intro to appceleratorIntro to appcelerator
Intro to appcelerator
 
Do You Enjoy Espresso in Android App Testing?
Do You Enjoy Espresso in Android App Testing?Do You Enjoy Espresso in Android App Testing?
Do You Enjoy Espresso in Android App Testing?
 
ITB2015 - Crash Course in Ionic + AngularJS
ITB2015 - Crash Course in Ionic + AngularJSITB2015 - Crash Course in Ionic + AngularJS
ITB2015 - Crash Course in Ionic + AngularJS
 
Appurify process
Appurify processAppurify process
Appurify process
 
Crash Course in AngularJS + Ionic (Deep dive)
Crash Course in AngularJS + Ionic (Deep dive)Crash Course in AngularJS + Ionic (Deep dive)
Crash Course in AngularJS + Ionic (Deep dive)
 
Continuous Integration, Deploy, Test From Beginning To End 2014
Continuous Integration, Deploy, Test From Beginning To End 2014Continuous Integration, Deploy, Test From Beginning To End 2014
Continuous Integration, Deploy, Test From Beginning To End 2014
 

More from Yelp Engineering

Teeing Up Python - Code Golf
Teeing Up Python - Code GolfTeeing Up Python - Code Golf
Teeing Up Python - Code GolfYelp Engineering
 
Building a World Class Security Team
Building a World Class Security TeamBuilding a World Class Security Team
Building a World Class Security TeamYelp Engineering
 
Ensuring Consistency in a Replicated World
Ensuring Consistency in a Replicated WorldEnsuring Consistency in a Replicated World
Ensuring Consistency in a Replicated WorldYelp Engineering
 
A Beginners Guide To Launching Yelp In Hong Kong
A Beginners Guide To Launching Yelp In Hong KongA Beginners Guide To Launching Yelp In Hong Kong
A Beginners Guide To Launching Yelp In Hong KongYelp Engineering
 
Scaling Traffic from 0 to 139 Million Unique Visitors
Scaling Traffic from 0 to 139 Million Unique VisitorsScaling Traffic from 0 to 139 Million Unique Visitors
Scaling Traffic from 0 to 139 Million Unique VisitorsYelp Engineering
 
Optimal Learning for Fun and Profit with MOE
Optimal Learning for Fun and Profit with MOEOptimal Learning for Fun and Profit with MOE
Optimal Learning for Fun and Profit with MOEYelp Engineering
 
"Using ElasticSearch to Scale Near Real-Time Search" by John Billings (Presen...
"Using ElasticSearch to Scale Near Real-Time Search" by John Billings (Presen..."Using ElasticSearch to Scale Near Real-Time Search" by John Billings (Presen...
"Using ElasticSearch to Scale Near Real-Time Search" by John Billings (Presen...Yelp Engineering
 
"Optimal Learning for Fun and Profit" by Scott Clark (Presented at The Yelp E...
"Optimal Learning for Fun and Profit" by Scott Clark (Presented at The Yelp E..."Optimal Learning for Fun and Profit" by Scott Clark (Presented at The Yelp E...
"Optimal Learning for Fun and Profit" by Scott Clark (Presented at The Yelp E...Yelp Engineering
 

More from Yelp Engineering (13)

Human Ops
Human OpsHuman Ops
Human Ops
 
Teeing Up Python - Code Golf
Teeing Up Python - Code GolfTeeing Up Python - Code Golf
Teeing Up Python - Code Golf
 
Fluxx Streaming
Fluxx StreamingFluxx Streaming
Fluxx Streaming
 
Giving Design Critique
Giving Design CritiqueGiving Design Critique
Giving Design Critique
 
Building a World Class Security Team
Building a World Class Security TeamBuilding a World Class Security Team
Building a World Class Security Team
 
Ensuring Consistency in a Replicated World
Ensuring Consistency in a Replicated WorldEnsuring Consistency in a Replicated World
Ensuring Consistency in a Replicated World
 
A Beginners Guide To Launching Yelp In Hong Kong
A Beginners Guide To Launching Yelp In Hong KongA Beginners Guide To Launching Yelp In Hong Kong
A Beginners Guide To Launching Yelp In Hong Kong
 
MySQL At Yelp
MySQL At YelpMySQL At Yelp
MySQL At Yelp
 
Own Your Career
Own Your CareerOwn Your Career
Own Your Career
 
Scaling Traffic from 0 to 139 Million Unique Visitors
Scaling Traffic from 0 to 139 Million Unique VisitorsScaling Traffic from 0 to 139 Million Unique Visitors
Scaling Traffic from 0 to 139 Million Unique Visitors
 
Optimal Learning for Fun and Profit with MOE
Optimal Learning for Fun and Profit with MOEOptimal Learning for Fun and Profit with MOE
Optimal Learning for Fun and Profit with MOE
 
"Using ElasticSearch to Scale Near Real-Time Search" by John Billings (Presen...
"Using ElasticSearch to Scale Near Real-Time Search" by John Billings (Presen..."Using ElasticSearch to Scale Near Real-Time Search" by John Billings (Presen...
"Using ElasticSearch to Scale Near Real-Time Search" by John Billings (Presen...
 
"Optimal Learning for Fun and Profit" by Scott Clark (Presented at The Yelp E...
"Optimal Learning for Fun and Profit" by Scott Clark (Presented at The Yelp E..."Optimal Learning for Fun and Profit" by Scott Clark (Presented at The Yelp E...
"Optimal Learning for Fun and Profit" by Scott Clark (Presented at The Yelp E...
 

Recently uploaded

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
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
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
 
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
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfEnterprise Knowledge
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure servicePooja Nehwal
 
🐬 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
 
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
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
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
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationRidwan Fadjar
 
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
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitecturePixlogix Infotech
 
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
 

Recently uploaded (20)

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...
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
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
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
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 ...
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 Presentation
 
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...
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC Architecture
 
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
 

Yelp Tech Talks: Mobile Testing 1, 2, 3

  • 2.
  • 3. Building Yelp for Apple Watch Bill Meltsner wmeltsner@yelp.com @billmeltsner
  • 4. Today Initial Scoping / Planning Yelp.app on Apple Watch Deep Dive Lessons Learned
  • 5. Who I Am iOS Technical Lead Yelp on Apple Watch project lead Worked largely on Watch app logic
  • 6. Who We Were Designer Engineer Product Manager Engineer (Intern)
  • 7. Why Build Yelp Watch App? Yelp everywhere Day-one advantage Knew we could build a great experience
  • 9. Initial Scoping unlimited time* = unlimited features *this never happens
  • 10. Initial Scoping finite time = finite features
  • 11. Initial Scoping Product team defined features Engineering team estimated available time Worked together to define MVP as cost of time versus feature
  • 12. Initial Scoping UI & Logic could be parallelized - 1 engineer for each Milestones for MVP, MVP+1, MVP+2, … Flexibility in our schedule to add / remove features and stay agile
  • 13. Demo
  • 15. WatchKit - A Changing Landscape Brand new platform with docs & APIs changing drastically between betas No defined best practices Focused on making best-effort technical decisions with the ability to refine later
  • 16. Overview of a WatchKit app Parent App API Requests Watch App Storyboard WatchKit Extension Location Logic Interface Control Images iPhone Apple Watch
  • 17. Interface Controllers View Controller analog Interface hierarchy is fixed YPWKSearchResultsInterfaceController
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 29. Networking API requests owned by parent app Image loading owned by extension
  • 30. Location Owned by extension – runs in foreground, parent app runs in background We only request foreground access
  • 31. Location Permissions belong to parent app, must be granted on phone
  • 32. Phone ↔ Watch Communications All calls in one iteration of the run loop coalesced together Communication between watch and phone is rate-limited serial queue Overhead is high – batch your calls!
  • 33. Images Key part of our search UI Naive approach: send each image to the watch as it’s loaded Result: traffic jam of communications, unresponsive app
  • 34. Images Solution: Wait x seconds, send all images loaded in that timeframe at once Problem: that can be a lot of data Solution Part 2: crush the heck out of ‘em UIImageJPEGRepresentation(image, 0.0) // max compression
  • 36. Think like a Startup Priority 1 was being there on launch day MVP comes first, everything else can wait Technical debt is not inherently bad
  • 37. Plan Ahead Define designs and scope before writing any code New platforms are hard to predict effectively Bend but don’t break
  • 39. Testing The Yelp App iOS & Android
  • 40. Who We Are Mason Glidden ● iOS Engineer ● iOS Testing o KIF & Jenkins ● mglidden@yelp.com Tim Mellor ● Android Engineer ● Android Testing o Espresso & Jenkins
  • 41. How we develop new mobile APIs iOS & Android - Tests & Testing Strategy Today
  • 42. Building New Mobile APIs How we use our documentation to test new APIs
  • 43. Mobile APIs @ Yelp ● API shared by iOS & Android ● New APIs start with documentation and examples ● Client and API can be developed simultaneously ● API team manages backwards compatibility tests
  • 44. /* h2. Photo (full) |_. Name |_. Type |_. Description | | id | string | Identifier | | time_created | time | Timestamp for when photo was uploaded | | url_prefix | string | Prefix for image url | | user_passport | "Passport":{{site.url}}/v1/objects/passport/index.html | Passport for user who uploaded photo (not provided for photos added by biz owners to their own biz from their biz owner account) (Optional) | | caption | string | Caption | | feedback_positive_count | integer | Number of likes for the photo | | */ { "id": "PHOTO_ID123", "time_created": 1370985832, "user_passport": {% include_jsondoc "v1/objects/passport/default.json" Passport %}, "url_prefix": "http://s3- media4.ak.yelpcdn.com/bphoto/_Xwa-lWTpivnlB1tgX-sJw/", "caption": "Korean tacos ($5.75)", "feedback_positive_count": 19, } Documentation
  • 45. /* h2. Photo (full) |_. Name |_. Type |_. Description | | id | string | Identifier | | time_created | time | Timestamp for when photo was uploaded | | url_prefix | string | Prefix for image url | | user_passport | "Passport":{{site.url}}/v1/objects/passport/index.html | Passport for user who uploaded photo (not provided for photos added by biz owners to their own biz from their biz owner account) (Optional) | | caption | string | Caption | | feedback_positive_count | integer | Number of likes for the photo | | */ { "id": "PHOTO_ID123", "time_created": 1370985832, "user_passport": {% include_jsondoc "v1/objects/passport/default.json" Passport %}, "url_prefix": "http://s3- media4.ak.yelpcdn.com/bphoto/_Xwa-lWTpivnlB1tgX-sJw/", "caption": "Korean tacos ($5.75)", "feedback_positive_count": 19, } Documentation Textile
  • 46. /* h2. Photo (full) |_. Name |_. Type |_. Description | | id | string | Identifier | | time_created | time | Timestamp for when photo was uploaded | | url_prefix | string | Prefix for image url | | user_passport | "Passport":{{site.url}}/v1/objects/passport/index.html | Passport for user who uploaded photo (not provided for photos added by biz owners to their own biz from their biz owner account) (Optional) | | caption | string | Caption | | feedback_positive_count | integer | Number of likes for the photo | | */ { "id": "PHOTO_ID123", "time_created": 1370985832, "user_passport": {% include_jsondoc "v1/objects/passport/default.json" Passport %}, "url_prefix": "http://s3- media4.ak.yelpcdn.com/bphoto/_Xwa-lWTpivnlB1tgX-sJw/", "caption": "Korean tacos ($5.75)", "feedback_positive_count": 19, } Documentation JSONDoc Textile
  • 47. Documentation /* h2. Photo (full) |_. Name |_. Type |_. Description | | id | string | Identifier | | time_created | time | Timestamp for when photo was uploaded | | url_prefix | string | Prefix for image url | | user_passport | "Passport":{{site.url}}/v1/objects/passport/index.html | Passport for user who uploaded photo (not provided for photos added by biz owners to their own biz from their biz owner account) (Optional) | | caption | string | Caption | | feedback_positive_count | integer | Number of likes for the photo | | */ { "id": "PHOTO_ID123", "time_created": 1370985832, "user_passport": {% include_jsondoc "v1/objects/passport/default.json" Passport %}, "url_prefix": "http://s3- media4.ak.yelpcdn.com/bphoto/_Xwa-lWTpivnlB1tgX-sJw/", "caption": "Korean tacos ($5.75)", "feedback_positive_count": 19, }
  • 48. Documentation -> JSON ● Included as submodule in client repos ● Build step to flatten documentation into JSON (e.g. v1- -objects--photo+full.json) ● Code requests specific mocks
  • 49. Why This Approach Works for Us ● API & client contract ● Fewer dependencies for developers & Jenkins ● Improved test speed & reliability on iOS & Android
  • 50. iOS Testing @ Yelp Mason Glidden
  • 51. ● Prevent Regressions ● Give developers confidence ● Run quickly ● Reliable results ● Easy to write Test Goals
  • 52. ● Unit Tests ● Integration Tests ● Acceptance Tests Test Types
  • 53. ● Prevent UI & Logic Regressions ● ~150 logic unit tests ● ~100 network request contract tests ● ~650 view tests ● Continuous Integration on Jenkins Unit Tests
  • 54. ● Generally pretty simple ● Test-Driven Development ● Super fast to run Logic Tests
  • 55. Example: Business Hours Logic - (void)testOpensSoon { // Test that "Opens soon" appears with the correct time interval [NSDate yk_setDate:[NSDate dateWithTimeIntervalSince1970:1356040364]]; NSArray *openHoursArray = @[@[@5160, @5460]]; OpenHours *openHours = [OpenHours openHoursFromJSON:openHoursArray timeZoneString:@"America/Los_Angeles"]; STAssertEqualObjects(@"Opens in 8 min", [openHours openOrClosedStringUsingMinutes:YES], nil); } Logic Tests
  • 56. ● Makes sure client can still parse documented API changes ● Example: ReviewsListRequestTest - (void)testList { ReviewsListRequest *request = [[ReviewsListRequest alloc] init]; [OHHTTPStubs yp_receiveFromPath:@"v1--reviews+reviews.json" statusCode:200 MIMEType:@"application/json" afterDelay:0.1]; [request listWithBusinessId:@"BIZID" selectedReviewId:nil offset:0 limit:10 delegate:self]; [self waitForStatus:YPAsyncTestWaitStatusSuccess timeout:10.0 requestToCancelOnTimeout:request]; } Parsing Tests
  • 57. View Tests ● View with mock data ● Screenshot of view ● Compares with previous versions ● Based off GHUnit
  • 59. View Tests ● Example: contribution buttons view test - (void)testBasicButtons { Business *business = [Business businessFromJSONDictionary: [YPDebug JSONFromResource:@"v1--objects--business+full.json"] request:nil context:nil]; YPBusinessContributeButtons *buttons = [[YPBusinessContributeButtons alloc] init]; [buttons setBusiness:business]; YPVerifyView(buttons); }
  • 60. View Tests ● Pros: ○ Easy way to catch regressions ○ Invaluable when refactoring or updating to new OS versions ● Cons: ○ Slow: ~¾ seconds per test ○ Lots of false-positive failures
  • 61. Integration Tests ● Testing that application behaves as expected ● Interaction between view controllers ● Primary signals of a problem: ○ Non-visual - analytics & network requests ○ Visual - button or label ● ~225 Integration tests
  • 62. KIF ● ~150 KIF tests ● Uses accessibility labels to navigate ● Custom hooks for analytics, requests ● Continuous integration on Jenkins ● Separate iPad and iPhone tests github.com/kif-framework/KIF
  • 64. Integration Test Example ● Example: ReviewCompositionIntegrationTest - (void)testReviewWrite { [YPAPI yp_addMockSessionConfirmed:YES sendNotification:YES]; [self yp_openBusinessViewController]; [tester tapViewWithAccessibilityLabel:@"Write a Review"]; [tester yp_waitForModalViewControllerOfClass:[ReviewComposeViewController class]]; [tester yp_waitForExpectedAnalytics:@[@{@"iri": kAnalyticsReviewWriteIRI, @"params": @{@"intended_compose_type": @"add"}]]; [tester tapViewWithAccessibilityLabel:@"Rating"]; [tester clearTextFromAndThenEnterText:@"My review" intoViewWithAccessibilityLabel:@"Write your review here."]; [tester tapViewWithAccessibilityLabel:@"Post"]; [tester yp_waitForRequestWithPath:@"/review/save" queryParams:nil postParams:@{ @"text": @"My review", @"rating": @"3" }]; }
  • 65. Integration Test Example ● Example: ReviewCompositionIntegrationTest - (void)testReviewWrite { [YPAPI yp_addMockSessionConfirmed:YES sendNotification:YES]; [self yp_openBusinessViewController]; [tester tapViewWithAccessibilityLabel:@"Write a Review"]; [tester yp_waitForModalViewControllerOfClass:[ReviewComposeViewController class]]; [tester yp_waitForExpectedAnalytics:@[@{@"iri": kAnalyticsReviewWriteIRI, @"params": @{@"intended_compose_type": @"add"}]]; [tester tapViewWithAccessibilityLabel:@"Rating"]; [tester clearTextFromAndThenEnterText:@"My review" intoViewWithAccessibilityLabel:@"Write your review here."]; [tester tapViewWithAccessibilityLabel:@"Post"]; [tester yp_waitForRequestWithPath:@"/review/save" queryParams:nil postParams:@{ @"text": @"My review", @"rating": @"3" }]; }
  • 66. Integration Test Example ● Example: ReviewCompositionIntegrationTest - (void)testReviewWrite { [YPAPI yp_addMockSessionConfirmed:YES sendNotification:YES]; [self yp_openBusinessViewController]; [tester tapViewWithAccessibilityLabel:@"Write a Review"]; [tester yp_waitForModalViewControllerOfClass:[ReviewComposeViewController class]]; [tester yp_waitForExpectedAnalytics:@[@{@"iri": kAnalyticsReviewWriteIRI, @"params": @{@"intended_compose_type": @"add"}]]; [tester tapViewWithAccessibilityLabel:@"Rating"]; [tester clearTextFromAndThenEnterText:@"My review" intoViewWithAccessibilityLabel:@"Write your review here."]; [tester tapViewWithAccessibilityLabel:@"Post"]; [tester yp_waitForRequestWithPath:@"/review/save" queryParams:nil postParams:@{ @"text": @"My review", @"rating": @"3" }]; }
  • 67. Integration Test Example ● Example: ReviewCompositionIntegrationTest - (void)testReviewWrite { [YPAPI yp_addMockSessionConfirmed:YES sendNotification:YES]; [self yp_openBusinessViewController]; [tester tapViewWithAccessibilityLabel:@"Write a Review"]; [tester yp_waitForModalViewControllerOfClass:[ReviewComposeViewController class]]; [tester yp_waitForExpectedAnalytics:@[@{@"iri": kAnalyticsReviewWriteIRI, @"params": @{@"intended_compose_type": @"add"}]]; [tester tapViewWithAccessibilityLabel:@"Rating"]; [tester clearTextFromAndThenEnterText:@"My review" intoViewWithAccessibilityLabel:@"Write your review here."]; [tester tapViewWithAccessibilityLabel:@"Post"]; [tester yp_waitForRequestWithPath:@"/review/save" queryParams:nil postParams:@{ @"text": @"My review", @"rating": @"3" }]; }
  • 68. Sandboxing Mocked During Tests: ● Networking ● Date, Time, Timezone ● Device permissions ● Singletons Between Test Runs: ● Clean caches ● Reset user defaults ● Reset navigation stack ● Device orientation
  • 69. Other Tooling ● OHHTTPStubs to block & mock network requests ● OCMock for mocking ● XCTool to run our tests
  • 70. Acceptance Tests ● Test overall look and feel ● ~50 manual test cases ○ Moving some to KIF ● iOS7 & 8, iPad & iPhone ● Run by Engineers + PM during release process
  • 71. Closing Thoughts ● API mocks make it easy for us to reliably grow our testing suite ● Different types of tests for different problems ● Sandboxes to create consistent environments ● KIF <3
  • 72. Android Testing @ Yelp Tim Mellor tfmellor@yelp.com
  • 73. tests = tools + code
  • 74.
  • 75. Simple, right? ● AndroidTestCase ● InstrumentationTestCase ● ApplicationTestCase ● ActivityTestCase ● ActivityUnitTestCase ● ActivityInstrumentationTestCase ● ActivityInstrumentationTestCase2
  • 77.
  • 78. Problem: Devices ● Devices are necessary ● Devices suck ● Virtual devices are bearable
  • 79. Solution: Devices ● Genymotion’s gmtool ● Clone image into new device ● Speed of Genymotion $ python gmtool_wrapper.py start --vms '{"18":1, "19":1, "21":1}'
  • 80. Problem: Flakes! ● Part 1: Instrumentation + Device ● Part 2: Test library
  • 82. Instrumentation consequences ● Uncaught exceptions halt test suite ● Activities/Services/etc. stay open
  • 83. Solution: Instrumentation Flakes ● One test per instrumentation run! $ adb shell pm clear com.yelp.droid
  • 84. Flakiness and Test libraries Robotium and its solo.waitFor* methods
  • 85. Android test kit to the rescue!
  • 89. Problem: slow test suites ● Consequence of needing devices ● Long = impractical
  • 90. Solution: test sharding $ adb shell am instrument -w -e numShards 4 -e shardIndex 1 ● github.com/shazam/fork ● Resources are the limit!
  • 91. Yelp Testing Process ● ~300 unit tests ● ~100 integration tests ● ~150 UI integration tests ● Manual testing against production ● Beta group ● 50% roll-out in Play Store
  • 92. UI Integration test toolkit @ Yelp ● Espresso! ● Home-rolled MockHttpClient o MockResponse o MockRequestMatcher ● Spoon
  • 93.
  • 94.
  • 95. public void test_ClickingBookmark_SendsAddRequestAndUpdatesUi() { mBusiness.setBookmarked(false); setActivityIntent(ActivityBusinessPage.intentForBusiness(getYelpContext(), mBusiness)); getActivity(); takeScreenshot("business detail unbookmarked"); // Check that the bookmark button has the text “Bookmark” and click on it // This should trigger a bookmark add request. onView(withId(R.id.bookmark)) .check(matches(allOf(isDisplayed(), withText(R.string.action_bookmark)))) .perform(click()); takeScreenshot("bookmarked"); // Make sure we hit bookmarks/add with the proper params. assertThat(mAddBookmarkResponse.getRequestCount(), is(1)); // Make sure the "Bookmarked" button now shows. onView(withId(R.id.bookmark)) .check(matches(allOf(isDisplayed(), withText(R.string.bookmarked)))); }
  • 96. public void test_ClickingBookmark_SendsAddRequestAndUpdatesUi() { mBusiness.setBookmarked(false); setActivityIntent(ActivityBusinessPage.intentForBusiness(getYelpContext(), mBusiness)); getActivity(); takeScreenshot("business detail unbookmarked"); // Check that the bookmark button has the text “Bookmark” and click on it // This should trigger a bookmark add request. onView(withId(R.id.bookmark)) .check(matches(allOf(isDisplayed(), withText(R.string.action_bookmark)))) .perform(click()); takeScreenshot("bookmarked"); // Make sure we hit bookmarks/add with the proper params. assertThat(mAddBookmarkResponse.getRequestCount(), is(1)); // Make sure the "Bookmarked" button now shows. onView(withId(R.id.bookmark)) .check(matches(allOf(isDisplayed(), withText(R.string.bookmarked)))); }
  • 97. public void test_ClickingBookmark_SendsAddRequestAndUpdatesUi() { mBusiness.setBookmarked(false); setActivityIntent(ActivityBusinessPage.intentForBusiness(getYelpContext(), mBusiness)); getActivity(); takeScreenshot("business detail unbookmarked"); // Check that the bookmark button has the text “Bookmark” and click on it // This should trigger a bookmark add request. onView(withId(R.id.bookmark)) .check(matches(allOf(isDisplayed(), withText(R.string.action_bookmark)))) .perform(click()); takeScreenshot("bookmarked"); // Make sure we hit bookmarks/add with the proper params. assertThat(mAddBookmarkResponse.getRequestCount(), is(1)); // Make sure the "Bookmarked" button now shows. onView(withId(R.id.bookmark)) .check(matches(allOf(isDisplayed(), withText(R.string.bookmarked)))); }
  • 98. public void test_ClickingBookmark_SendsAddRequestAndUpdatesUi() { mBusiness.setBookmarked(false); setActivityIntent(ActivityBusinessPage.intentForBusiness(getYelpContext(), mBusiness)); getActivity(); takeScreenshot("business detail unbookmarked"); // Check that the bookmark button has the text “Bookmark” and click on it // This should trigger a bookmark add request. onView(withId(R.id.bookmark)) .check(matches(allOf(isDisplayed(), withText(R.string.action_bookmark)))) .perform(click()); takeScreenshot("bookmarked"); // Make sure we hit bookmarks/add with the proper params. assertThat(mAddBookmarkResponse.getRequestCount(), is(1)); // Make sure the "Bookmarked" button now shows. onView(withId(R.id.bookmark)) .check(matches(allOf(isDisplayed(), withText(R.string.bookmarked)))); }
  • 99.
  • 100.
  • 101. public void test_WriteTip_SendsProperRequest() { setMockSession(); MockResponse tipSaveResponse = mMockHttpClient.addDefaultMock( ApiPath.QUICKTIPS_SAVE, getTipSaveParams()); openTipPage(); onView(withId(R.id.edit_text)).perform(typeText(TIP_TEXT)); takeScreenshot("tip typed"); onView(withId(R.id.done_button)).perform(click()); takeScreenshot("business page"); // We should show a "Thanks for the tip!" dialog on the business page. onView(withText(R.string.thanks_for_the_tip)).check(matches(isDisplayed())); assertThat(tipSaveResponse.getRequestCount(), is(1)); }
  • 102. public void test_WriteTip_SendsProperRequest() { setMockSession(); MockResponse tipSaveResponse = mMockHttpClient.addDefaultMock( ApiPath.QUICKTIPS_SAVE, getTipSaveParams()); openTipPage(); onView(withId(R.id.edit_text)).perform(typeText(TIP_TEXT)); takeScreenshot("tip typed"); onView(withId(R.id.done_button)).perform(click()); takeScreenshot("business page"); // We should show a "Thanks for the tip!" dialog on the business page. onView(withText(R.string.thanks_for_the_tip)).check(matches(isDisplayed())); assertThat(tipSaveResponse.getRequestCount(), is(1)); }
  • 103. public void test_WriteTip_SendsProperRequest() { setMockSession(); MockResponse tipSaveResponse = mMockHttpClient.addDefaultMock( ApiPath.QUICKTIPS_SAVE, getTipSaveParams()); openTipPage(); onView(withId(R.id.edit_text)).perform(typeText(TIP_TEXT)); takeScreenshot("tip typed"); onView(withId(R.id.done_button)).perform(click()); takeScreenshot("business page"); // We should show a "Thanks for the tip!" dialog on the business page. onView(withText(R.string.thanks_for_the_tip)).check(matches(isDisplayed())); assertThat(tipSaveResponse.getRequestCount(), is(1)); }
  • 104. public void test_WriteTip_SendsProperRequest() { setMockSession(); MockResponse tipSaveResponse = mMockHttpClient.addDefaultMock( ApiPath.QUICKTIPS_SAVE, getTipSaveParams()); openTipPage(); onView(withId(R.id.edit_text)).perform(typeText(TIP_TEXT)); takeScreenshot("tip typed"); onView(withId(R.id.done_button)).perform(click()); takeScreenshot("business page"); // We should show a "Thanks for the tip!" dialog on the business page. onView(withText(R.string.thanks_for_the_tip)).check(matches(isDisplayed())); assertThat(tipSaveResponse.getRequestCount(), is(1)); }
  • 105.
  • 106.
  • 107. ● Library choices matter ● Address the issues at the source! ● Tests don’t have to be a pain Lessons learned