108
@YelpEngineering YelpEngineers engineeringblog.yel p.com github.com/yelp yelp.com/ careers

Yelp Tech Talks: Mobile Testing 1, 2, 3

Embed Size (px)

Citation preview

Page 1: Yelp Tech Talks: Mobile Testing 1, 2, 3

@YelpEngineering

YelpEngineers

engineeringblog.yelp.com

github.com/yelpyelp.com/careers

Page 2: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 3: Yelp Tech Talks: Mobile Testing 1, 2, 3

Building Yelp for Apple WatchBill Meltsner

[email protected]@billmeltsner

Page 4: Yelp Tech Talks: Mobile Testing 1, 2, 3

Today

Initial Scoping / Planning

Yelp.app on Apple Watch Deep Dive

Lessons Learned

Page 5: Yelp Tech Talks: Mobile Testing 1, 2, 3

Who I Am

iOS Technical Lead

Yelp on Apple Watch project lead

Worked largely on Watch app logic

Page 6: Yelp Tech Talks: Mobile Testing 1, 2, 3

Who We Were

Designer Engineer Product Manager Engineer (Intern)

Page 7: Yelp Tech Talks: Mobile Testing 1, 2, 3

Why Build Yelp Watch App?

Yelp everywhere

Day-one advantage

Knew we could build a great experience

Page 8: Yelp Tech Talks: Mobile Testing 1, 2, 3

Initial Scoping

time = features

Page 9: Yelp Tech Talks: Mobile Testing 1, 2, 3

Initial Scoping

unlimited time* = unlimited features

*this never happens

Page 10: Yelp Tech Talks: Mobile Testing 1, 2, 3

Initial Scoping

finite time = finite features

Page 11: Yelp Tech Talks: Mobile Testing 1, 2, 3

Initial Scoping

Product team defined features

Engineering team estimated available time

Worked together to define MVP as cost of time versus feature

Page 12: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 13: Yelp Tech Talks: Mobile Testing 1, 2, 3

Demo

Page 14: Yelp Tech Talks: Mobile Testing 1, 2, 3

Yelp Watch AppTechnical Overview

Page 15: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 16: Yelp Tech Talks: Mobile Testing 1, 2, 3

Overview of a WatchKit app

Parent App

API Requests

Watch App

Storyboard

WatchKit Extension

Location

Logic

Interface Control

Images

iPhone Apple Watch

Page 17: Yelp Tech Talks: Mobile Testing 1, 2, 3

Interface Controllers

View Controller analog

Interface hierarchy is fixed

YPWKSearchResultsInterfaceController

Page 18: Yelp Tech Talks: Mobile Testing 1, 2, 3

Storyboard Overview

Page 19: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 20: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 21: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 22: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 23: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 24: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 25: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 26: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 27: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 28: Yelp Tech Talks: Mobile Testing 1, 2, 3

Before After

Page 29: Yelp Tech Talks: Mobile Testing 1, 2, 3

Networking

API requests owned by parent app

Image loading owned by extension

Page 30: Yelp Tech Talks: Mobile Testing 1, 2, 3

Location

Owned by extension – runs in foreground, parent app runs in background

We only request foreground access

Page 31: Yelp Tech Talks: Mobile Testing 1, 2, 3

Location

Permissions belong to parent app, must be granted on phone

Page 32: Yelp Tech Talks: Mobile Testing 1, 2, 3

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!

Page 33: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 34: Yelp Tech Talks: Mobile Testing 1, 2, 3

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 ‘emUIImageJPEGRepresentation(image, 0.0) // max

compression

Page 35: Yelp Tech Talks: Mobile Testing 1, 2, 3

Lessons Learned

Page 36: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 37: Yelp Tech Talks: Mobile Testing 1, 2, 3

Plan Ahead

Define designs and scope before writing any code

New platforms are hard to predict effectively

Bend but don’t break

Page 38: Yelp Tech Talks: Mobile Testing 1, 2, 3

Questions?

Page 39: Yelp Tech Talks: Mobile Testing 1, 2, 3

Testing The Yelp AppiOS & Android

Page 40: Yelp Tech Talks: Mobile Testing 1, 2, 3

Who We Are

Mason Glidden● iOS Engineer● iOS Testing

o KIF & Jenkins● [email protected]

Tim Mellor● Android Engineer● Android Testing

o Espresso & Jenkins

Page 41: Yelp Tech Talks: Mobile Testing 1, 2, 3

How we develop new mobile APIs

iOS & Android - Tests & Testing Strategy

Today

Page 42: Yelp Tech Talks: Mobile Testing 1, 2, 3

Building New Mobile APIsHow we use our documentation to test new APIs

Page 43: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 44: Yelp Tech Talks: Mobile Testing 1, 2, 3

/*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

Page 45: Yelp Tech Talks: Mobile Testing 1, 2, 3

/*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

Page 46: Yelp Tech Talks: Mobile Testing 1, 2, 3

/*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

Page 47: Yelp Tech Talks: Mobile Testing 1, 2, 3

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,}

Page 48: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 49: Yelp Tech Talks: Mobile Testing 1, 2, 3

Why This Approach Works for Us

● API & client contract● Fewer dependencies for developers & Jenkins● Improved test speed & reliability on iOS &

Android

Page 50: Yelp Tech Talks: Mobile Testing 1, 2, 3

iOS Testing @ Yelp Mason Glidden

Page 51: Yelp Tech Talks: Mobile Testing 1, 2, 3

● Prevent Regressions● Give developers confidence● Run quickly● Reliable results● Easy to write

Test Goals

Page 52: Yelp Tech Talks: Mobile Testing 1, 2, 3

● Unit Tests● Integration Tests● Acceptance Tests

Test Types

Page 53: Yelp Tech Talks: Mobile Testing 1, 2, 3

● Prevent UI & Logic Regressions● ~150 logic unit tests● ~100 network request contract

tests● ~650 view tests● Continuous Integration on Jenkins

Unit Tests

Page 54: Yelp Tech Talks: Mobile Testing 1, 2, 3

● Generally pretty simple● Test-Driven Development● Super fast to run

Logic Tests

Page 55: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 56: Yelp Tech Talks: Mobile Testing 1, 2, 3

● 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

Page 57: Yelp Tech Talks: Mobile Testing 1, 2, 3

View Tests

● View with mock data

● Screenshot of view● Compares with

previous versions● Based off GHUnit

Page 59: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 60: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 61: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 62: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 63: Yelp Tech Talks: Mobile Testing 1, 2, 3

Integration Test Example

Page 64: Yelp Tech Talks: Mobile Testing 1, 2, 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" }];}

Page 65: Yelp Tech Talks: Mobile Testing 1, 2, 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" }];}

Page 66: Yelp Tech Talks: Mobile Testing 1, 2, 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" }];}

Page 67: Yelp Tech Talks: Mobile Testing 1, 2, 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" }];}

Page 68: Yelp Tech Talks: Mobile Testing 1, 2, 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

Page 69: Yelp Tech Talks: Mobile Testing 1, 2, 3

Other Tooling

● OHHTTPStubs to block & mock network requests

● OCMock for mocking● XCTool to run our tests

Page 70: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 71: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 72: Yelp Tech Talks: Mobile Testing 1, 2, 3

Android Testing @ YelpTim Mellor

[email protected]

Page 73: Yelp Tech Talks: Mobile Testing 1, 2, 3

tests = tools + code

Page 74: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 75: Yelp Tech Talks: Mobile Testing 1, 2, 3

Simple, right?

● AndroidTestCase● InstrumentationTestCase● ApplicationTestCase● ActivityTestCase● ActivityUnitTestCase● ActivityInstrumentationTestCase● ActivityInstrumentationTestCase2

Page 76: Yelp Tech Talks: Mobile Testing 1, 2, 3

More decisions

Page 77: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 78: Yelp Tech Talks: Mobile Testing 1, 2, 3

Problem: Devices

● Devices are necessary

● Devices suck● Virtual devices

are bearable

Page 79: Yelp Tech Talks: Mobile Testing 1, 2, 3

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}'

Page 80: Yelp Tech Talks: Mobile Testing 1, 2, 3

Problem: Flakes!

● Part 1: Instrumentation + Device● Part 2: Test library

Page 81: Yelp Tech Talks: Mobile Testing 1, 2, 3

Android Instrumentation

Page 82: Yelp Tech Talks: Mobile Testing 1, 2, 3

Instrumentation consequences

● Uncaught exceptions halt test suite● Activities/Services/etc. stay open

Page 83: Yelp Tech Talks: Mobile Testing 1, 2, 3

Solution: Instrumentation Flakes

● One test per instrumentation run!$ adb shell pm clear com.yelp.droid

Page 84: Yelp Tech Talks: Mobile Testing 1, 2, 3

Flakiness and Test libraries

Robotium and its solo.waitFor* methods

Page 85: Yelp Tech Talks: Mobile Testing 1, 2, 3

Android test kit to the rescue!

Page 86: Yelp Tech Talks: Mobile Testing 1, 2, 3

Main Thread

Page 87: Yelp Tech Talks: Mobile Testing 1, 2, 3

click()

Espresso

blocked

Main Thread

Test thread

assertionsTest

Page 88: Yelp Tech Talks: Mobile Testing 1, 2, 3

click()

Espresso

blocked

Main Thread

Test thread

assertionsTest

Task ThreadBackground task

Page 89: Yelp Tech Talks: Mobile Testing 1, 2, 3

Problem: slow test suites

● Consequence of needing devices● Long = impractical

Page 90: Yelp Tech Talks: Mobile Testing 1, 2, 3

Solution: test sharding

$ adb shell am instrument -w \ -e numShards 4 \ -e shardIndex 1● github.com/shazam/fork● Resources are the limit!

Page 91: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 92: Yelp Tech Talks: Mobile Testing 1, 2, 3

UI Integration test toolkit @ Yelp

● Espresso!● Home-rolled MockHttpClient

o MockResponseo MockRequestMatcher

● Spoon

Page 93: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 94: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 95: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 96: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 97: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 98: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 100: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 101: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 102: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 103: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 104: Yelp Tech Talks: Mobile Testing 1, 2, 3

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

Page 106: Yelp Tech Talks: Mobile Testing 1, 2, 3
Page 107: Yelp Tech Talks: Mobile Testing 1, 2, 3

● Library choices matter● Address the issues at the source!● Tests don’t have to be a pain

Lessons learned

Page 108: Yelp Tech Talks: Mobile Testing 1, 2, 3

Questions?