1 of 61

2 of 61

What’s new in Android Testing

github.com/googlesamples/android-testing

3 of 61

Developer Platforms Engineer @Google

+Stephan Linzner

@onlythoughtwork

4 of 61

The next generation of Android Testing Tools

Unit Test Support

AndroidJUnitRunner�Espresso

Espresso-Intents

5 of 61

Unit Test Support�a.k.a JVM Tests�a.k.a Local Tests

6 of 61

Before unit test support

Running tests on device/emulator was slowBuild, deploy, make an espresso, run tests

Stub implementations in android.jar

Error java.lang.RuntimeException: Stub!

Framework limitations

Final classes

7 of 61

Unit test support for the Android Gradle Plugin

com.android.tools.build:gradle:1.1+

New source set test/ for unit tests

Mockable android.jar

Mockito to stub dependencies into the Android framework

8 of 61

apply plugin: 'com.android.application'��android {... testOptions { � unitTests.returnDefaultValues = true // Caution!}}��dependencies {// Unit testing dependencies� testCompile 'junit:junit:4.12'� testCompile 'org.mockito:mockito-core:1.10.19'}

app/build.gradle

9 of 61

Unit test sample

@RunWith(MockitoJUnitRunner.class)@SmallTest

public class UnitTestSample {�� private static final String FAKE_STRING = "HELLO WORLD";�� @MockContext mMockContext;�� @Testpublic void readStringFromContext_LocalizedString() {// Given a mocked Context injected into the object under test...when(mMockContext.getString(R.string.hello_word)).thenReturn(FAKE_STRING);ClassUnderTest myObjectUnderTest = new ClassUnderTest(mMockContext);�� // ...when the string is returned from the object under test...String result = myObjectUnderTest.getHelloWorldString();�� // ...then the result should be the expected one.� assertThat(result, is(FAKE_STRING));}

}

Dependency

Injection

Use MockitoJUnitRunner for

easier initialization of @Mock fields.

10 of 61

Command-line

$ ./gradlew test

...

:app:mockableAndroidJar

...

:app:assembleDebugUnitTest

:app:testDebug

:app:test

BUILD SUCCESSFUL

Total time: 3.142 secs

Android Studio

11 of 61

Reports

app/build/reports/tests/debug/index.html

app/build/test-results/debug/TEST-com.example.android.testing.unittesting.BasicSample.EmailValidatorTest.xml

New in 1.1+, �XML Reports

12 of 61

Limitations

Tight coupling with Android

When Mockito is not enough

Stubbing static methods

TextUtils.*, Log.*, etc.

13 of 61

Workarounds

Tight coupling with Android

Revisit your design decisions�Run unit tests on device or emulator

Stubbing static methods

Wrapper

PowerMockito�unitTests.returnDefaultValues = true

14 of 61

Android Testing Support Library

�a.k.a Instrumentation Tests

a.k.a Device or Emulator Tests

3

15 of 61

Instrumentation Tests

Run on Device or Emulator

Run With AndroidJUnitRunner�Located androidTest/ source set

16 of 61

Android SDK Manager

Open Sdk Manager�from Android Studio

Download latest

Support Repository

17 of 61

apply plugin: 'com.android.application'��android {� ...� defaultConfig {� …

testInstrumentationRunner ‘android.support.test.runner.AndroidJUnitRunner’}��}�dependencies {// AndroidJUnit Runner dependencies� androidTestCompile 'com.android.support.test:runner:0.2'}

app/build.gradle

18 of 61

Command-line

$./gradlew connectedAndroidTest�...

:app:assembleDebug�...

:app:assembleDebugAndroidTest

:app:connectedAndroidTest

BUILD SUCCESSFUL

Total time: 59.152 secs

Android Studio

App Under Test

Android Test App

19 of 61

Reports

app/build/outputs/reports/androidTests/connected/index.htmlapp/build/outputs/androidTest-results/connected/TEST-Nexus-6-5.1-app-flavor.xml

New in 1.1+, �XML Reports

20 of 61

AndroidJUnitRunner

21 of 61

AndroidJUnitRunner

A new test runner for Android

JUnit3/JUnit4 Support�Instrumentation Registry�Test Filtering�Intent Monitoring/Stubbing

Activity/Application Lifecycle Monitoring

22 of 61

JUnit4

@RunWith(AndroidJUnit4.class)@SmallTestpublic class DroidconItalyTest {Droidcon mDroidcon;�� @Beforepublic void initDroidcon() {� mDroidcon = Droidcons.get(Edition.ITALY);� mDroidcon.init();}�� @Testpublic void droidcon_IsAwesome_ReturnsTrue() {� assertThat(mDroidcon.isAwesome(), is(true));}� � @Afterpublic void releaseDroidcon() {� mDroidcon.release();}}

Use @Before to

setup your test fixture

Annotate all tests with @Test

Use @After to

release any resource

JUnit4 test need to be

annotated with AndroidJUnit4.class

23 of 61

Instrumentation Registry

@Beforepublic void accessAllTheThings() {� mArgsBundle = InstrumentationRegistry.getArguments();� mInstrumentation = InstrumentationRegistry.getInstrumentation();� mTestAppContext = InstrumentationRegistry.getContext();� mTargetContext = InstrumentationRegistry.getTargetContext();}

24 of 61

Test Filters

@SdkSuppress(minSdkVersion=15)@Testpublic void featureWithMinSdk15() {...}��@RequiresDevice@Testpublic void SomeDeviceSpecificFeature() {...}

Suppress test to run on certain�target Api levels

Filter tests that can only run on a (physical) device

25 of 61

JUnit4 Rules

26 of 61

apply plugin: 'com.android.application'��android {� ...� defaultConfig {� …

testInstrumentationRunner ‘android.support.test.runner.AndroidJUnitRunner’}��}�dependencies {// AndroidJUnit Runner dependenciesandroidTestCompile 'com.android.support.test:runner:0.2'

androidTestCompile 'com.android.support.test:rules:0.2'

}

app/build.gradle

27 of 61

@Deprecatedpublic class ActivityInstrumentationTestCase2

28 of 61

After

Before

ActivityInstrumentationTestCase2 vs. ActivityTestRule

29 of 61

ActivityTestRule Sample

https://github.com/googlesamples/android-testing/tree/master/espresso/BasicSample

@RunWith(AndroidJUnit4.class)@LargeTestpublic class ChangeTextBehaviorTest {�� ...�� @Rulepublic ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);�� @Testpublic void changeText_sameActivity() {// Type text and then press the button.� onView(withId(R.id.editTextUserInput)).perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard());� onView(withId(R.id.changeTextBt)).perform(click());�� // Check that the text was changed.� onView(withId(R.id.textToBeChanged)).check(matches(withText(STRING_TO_BE_TYPED)));}}

Use @Rule annotation

Create an ActivityTestRule�for your Activity

30 of 61

ActivityTestRule

public class ActivityTestRule<T extends Activity> extends UiThreadTestRule {�� public T getActivity() {}

public void launchActivity(Intent) {}

protected Intent getActivityIntent() {}�� protected void beforeActivityLaunched() {}�� protected void afterActivityFinished() {}��}

Lazy Launch of Activity�Custom Start Intent/Test

Access Activity instance

Override Activity Start Intent

31 of 61

ServiceTestRule Sample

@RunWith(AndroidJUnit4.class)@MediumTestpublic class MyServiceTest {�� @Rulepublic final ServiceTestRule mServiceRule = new ServiceTestRule();�� @Testpublic void testWithStartedService() {� mServiceRule.startService(new Intent(InstrumentationRegistry.getTargetContext(), MyService.class));// test code}�� @Testpublic void testWithBoundService() {IBinder binder = mServiceRule.bindService(new Intent(InstrumentationRegistry.getTargetContext(), MyService.class));MyService service = ((MyService.LocalBinder) binder).getService();� assertTrue("True wasn't returned", service.doSomethingToReturnTrue());}}

Use @Rule annotation

Create the�ServiceTestRule

Start Service under Test

Bind to Service under Test

32 of 61

Espresso

33 of 61

A new approach to UI Testing

34 of 61

An API from Developers for Developers

35 of 61

What would a user do?

Find a view

Perform an action�Check some state

36 of 61

onView(Matcher)

.perform(ViewAction)

.check(ViewAssertion);

Find, Perform, Check

37 of 61

38 of 61

apply plugin: 'com.android.application'��android {� ...� defaultConfig {� …

testInstrumentationRunner ‘android.support.test.runner.AndroidJUnitRunner’}��}�dependencies { androidTestCompile 'com.android.support.test:runner:0.2'

androidTestCompile 'com.android.support.test:rules:0.2'

// Espresso dependencies

androidTestCompile 'com.android.support.test.espresso:espresso-core:2.1'}

app/build.gradle

39 of 61

Espresso BasicSample

https://github.com/googlesamples/android-testing/tree/master/espresso/BasicSample

@RunWith(AndroidJUnit4.class)@LargeTestpublic class ChangeTextBehaviorTest { ...@Rulepublic ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);�� @Testpublic void changeText_sameActivity() {� onView(withId(R.id.editTextUserInput)).perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard());� onView(withId(R.id.changeTextBt)).perform(click());�� onView(withId(R.id.textToBeChanged)).check(matches(withText(STRING_TO_BE_TYPED)));}}

Type text and then press the button.

Check that text was changed

Find EditText view

Start Activity Under Test

40 of 61

Espresso APIs

onData() API for Adapter Views

Multi Window Support�Synchronization APIs

41 of 61

Espresso Contrib APIs

DrawerActions

RecyclerViewActions�[Time/Date]PickerActions

42 of 61

apply plugin: 'com.android.application'��android {� ...� defaultConfig {� …

testInstrumentationRunner ‘android.support.test.runner.AndroidJUnitRunner’}��}�dependencies { androidTestCompile 'com.android.support.test:runner:0.2'

androidTestCompile 'com.android.support.test:rules:0.2'

// Espresso dependencies

androidTestCompile 'com.android.support.test.espresso:espresso-core:2.1'

androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.1'}

app/build.gradle

43 of 61

Espresso-Intents

44 of 61

Espresso-Intents is like Mockito but for Intents

45 of 61

Hermetic inter-app testing

46 of 61

Hermetic Testing

Intent

Activity Result

Process:�com.android.camera

?

Process:�your.package.name

Open Camera

47 of 61

intended(IntentMatcher);

Intent Validation

48 of 61

intending(IntentMatcher)

.respondWith(ActivityResult);

Intent Stubbing

49 of 61

apply plugin: 'com.android.application'��android {� ...� defaultConfig {� …

testInstrumentationRunner ‘android.support.test.runner.AndroidJUnitRunner’}��}�dependencies { androidTestCompile 'com.android.support.test:runner:0.2'

androidTestCompile 'com.android.support.test:rules:0.2'

// Espresso dependencies

androidTestCompile 'com.android.support.test.espresso:espresso-core:2.1'

androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.1'}

app/build.gradle

50 of 61

IntentsBasicSample

https://github.com/googlesamples/android-testing/tree/master/espresso/IntentsBasicSample

@RunWith(AndroidJUnit4.class)@LargeTestpublic class DialerActivityTest {...@Rulepublic IntentsTestRule<DialerActivity> mRule = new IntentsTestRule<>(DialerActivity.class);�� @Testpublic void typeNumber_ValidInput_InitiatesCall() {� intending(not(isInternal())).respondWith(new ActivityResult(Activity.RESULT_OK, null));

onView(withId(R.id.edit_text_caller_number)).perform(typeText(VALID_PHONE_NUMBER),� closeSoftKeyboard());� onView(withId(R.id.button_call_number)).perform(click());

� intended(allOf(� hasAction(Intent.ACTION_CALL)),� hasData(INTENT_DATA_PHONE_NUMBER),� toPackage(PACKAGE_ANDROID_DIALER)));}}

Type Number and press Call Button

Verify Intent was sent

Create IntentsTestRule

Stub all external�Intents

51 of 61

UI Automator

52 of 61

UI Automator 2.0

Black box testing

Inter-app behavior testing

Context Access

53 of 61

apply plugin: 'com.android.application'��android {� ...� defaultConfig {� …

testInstrumentationRunner ‘android.support.test.runner.AndroidJUnitRunner’}��}�dependencies { androidTestCompile 'com.android.support.test:runner:0.2'

// UiAutomator Dependencies

androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.0.0'

}

app/build.gradle

54 of 61

UIAutomator BasicSample

https://github.com/googlesamples/android-testing/tree/master/uiautomator/BasicSample

@Beforepublic void startMainActivityFromHomeScreen() {�� mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());�� mDevice.pressHome();�� final String launcherPackage = getLauncherPackageName();� assertThat(launcherPackage, notNullValue());� mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT);�� Context context = InstrumentationRegistry.getContext();final Intent intent = context.getPackageManager().getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);� intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); � context.startActivity(intent);�� // Wait for the app to appear� mDevice.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)), LAUNCH_TIMEOUT);}

Initialize UiDevice

Launch Basic Sample

55 of 61

UIAutomator BasicSample

https://github.com/googlesamples/android-testing/tree/master/uiautomator/BasicSample

@Testpublic void testChangeText_sameActivity() {�� mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "editTextUserInput")).setText(STRING_TO_BE_TYPED);� mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "changeTextBt")).click();�� UiObject2 changedText = mDevice� .wait(Until.findObject(By.res(BASIC_SAMPLE_PACKAGE, "textToBeChanged")),500 /* wait 500ms */);� assertThat(changedText.getText(), is(equalTo(STRING_TO_BE_TYPED)));}

Type text and click Button

Verify text displayed�in UI

56 of 61

Contribute

57 of 61

Initialize your build environment

https://source.android.com/source/initializing.html

Install Repo

https://source.android.com/source/downloading.html

Checkout android-support-test branch

repo init -u https://android.googlesource.com/platform/manifest -g all -b android-support-test

Sync the source

repo sync -j24

Browse the source

cd frameworks/testing

Build and test

// Just build debug build type

./gradlew assembleDebug

// Run tests

./gradlew connectedCheck

Contribute to Android Testing Support Library

https://source.android.com/source/life-of-a-patch.html

58 of 61

Thank you

https://plus.google.com/+AndroidDevelopers/posts/jHXFkebKjEb�https://plus.google.com/+JoseAlcerreca/posts/3aU1J6EDGKd�https://github.com/googlesamples/android-testing

59 of 61

Q&A

60 of 61

#HappyTesting

61 of 61