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

DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupFlorian Wilhelm
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Enterprise Knowledge
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsMiki Katsuragi
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebUiPathCommunity
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 

Recently uploaded (20)

DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project Setup
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering Tips
 
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptxE-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio Web
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 

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