a guide to android automated testing

73
A guide to automated Android testing by @darrillaga

Upload: jotaemepereira

Post on 22-Jan-2018

579 views

Category:

Software


8 download

TRANSCRIPT

A guide to automated Android testing

by @darrillaga

Challenges

What to test

1. Utilities2. Android components3. UI4. Expected user behavior

What to test

1. Utilities2. Android components3. UI4. Expected user behavior

Utilities

Utilities are every piece of code, both pure java and Android-dependant, that are used as tools in the development process.

What to test

1. Utilities2. Android components3. UI4. Expected user behavior

Android components

1. Activities2. Fragments3. Services

These are the most common components.

What to test

1. Utilities2. Android components3. UI4. Expected user behavior

UI

UI specific behavior and custom views.

What to test

1. Utilities2. Android components3. UI4. Expected user behavior

Expected user behavior

Functional testing on our activities, based on expected behavior.

How to test

1. Using support libraries2. Stub code we can’t control while testing3. Decoupled code4. Testing everything that could be error-prone

How to test

1. Using support libraries2. Stub code we can’t control while testing3. Decoupled code4. Testing everything that could be error-prone

Using support libraries

1. Instrumentation2. Android JUnit Runner3. Espresso4. UIAutomator

Using support libraries

1. Instrumentation2. Android JUnit Runner3. Espresso4. UIAutomator

Instrumentation

This framework monitors all of the interaction between the Android system and the application.

Android testing libraries are built on top of the Instrumentation framework.

Using support libraries

1. Instrumentation2. Android JUnit Runner3. Espresso4. UIAutomator

AndroidJUnitRunner

JUnit test runner that lets you run JUnit 3 or JUnit 4-style test classes on Android devices, including those using the Espresso and UI Automator testing frameworks.

The test runner handles loading your test package and the app under test to a device, running your tests, and reporting test results.

Using support libraries

1. Instrumentation2. Android JUnit Runner3. Espresso4. UIAutomator

Espresso

APIs for writing UI tests to simulate user interactions within a single target app.

Provides automatic sync of test actions with the app’s UI.

Using support libraries

1. Instrumentation2. Android JUnit Runner3. Espresso4. UIAutomator

UI Automator

This framework provides a set of APIs to build UI tests that perform interactions on user apps and system apps.

The UI Automator APIs allows you to perform operations such as opening the Settings menu or the app launcher in a test device.

How to test

1. Using support libraries2. Stub code we can’t control while testing3. Decoupled code4. Testing everything that could be error-prone

Stub code we can’t control with testing

1. Robospice2. Card reader3. Thermal printer SDK4. Bluetooth

How to test

1. Using support libraries2. Stub code we can’t control while testing3. Decoupled code4. Testing everything that could be error-prone

Decoupled code

1. Android components only in charge of lifecycle

2. Layer for business logic3. Layer modeling views4. Create utilities for code that can be unit

tested

How to test

1. Using support libraries2. Stub code we can’t control while testing3. Decoupled code4. Testing everything that could be error-prone

Testing everything that could be error-prone

1. Do not test getter/setters2. Test code created by us3. Do not test third-party code (stub it or let it

be)

Wrapping up

Android Components

Utilities

Android system

UI

Stubs

Android Components

Utilities

Android system

UI

Stubs

Android Components

Utilities

Android system

UI

StubsUnit testing

● jUnit ● Instrumentation

jUnitUnit test

private EmailValidator subject = new EmailValidator();

@Testpublic void doesNotValidateWithInvalidEmail() { String email = "invalidEmail";

assertThat(subject.isValid(email), is(false));}

jUnit + InstrumentationUnit test

// This component uses the Android framework out of our control so we need to use instrumentation to provide a contextprivate CurrentUserSessionService subject = new CurrentUserSessionService(InstrumentationRegistry.getTargetContext());

@Testpublic void isCurrentSessionPresentReturnsTrueWithCurrentUser() { subject.saveUserSession(buildUserSession());

assertThat(subject.isCurrentSessionPresent(), is(true));}

private UserSession buildUserSession() { // creates stub user session instance}

Android Components

Utilities

Android system

UI

Stubs

Android Components

Utilities

Android system

UI

Stubs

Using manual or library-assisted dependency injection to switch dependencies for stubs when testing

Android Components

Utilities

Android system

UI

Stubs

Android Components

Utilities

Android system

UI

Stubs

Espresso to manipulate and unit-test views in an isolated Activity

Android Components

Utilities

Android system

UI

Stubs

Android Components

Utilities

Android system

UI

Stubs

UI automator

Functional test of interactions between the app and the Android system

Android Components

Utilities

Android system

UI

Stubs

Functional testing

● Espresso● Espresso Intents● Instrumentation● Stubs

Functional test

@Testpublic void unauthorizedUserLogin() { registerInvalidCredentialsLoginRequest();

setUserAndPassword();

clickOnSignIn();

Context context = InstrumentationRegistry.getTargetContext();

String errorMessage = context.getString(R.string.InvalidCredentialsError);

onView(allOf(withText(errorMessage), withParent(withId(R.id.errors_wrapper)))). check(matches(isDisplayed()));}

LoginActivity - Android components + Stubs + Espresso + Instrumentation

StubLoginActivity@Rulepublic SpicedTestRule<LoginActivity> mTestRule = new SpicedTestRule<>(LoginActivity.class);

private void registerInvalidCredentialsLoginRequest() { Charset utf8 = Charset.forName("utf-8");

mTestRule.registerResponseFor( LoginRequest.class, new HttpClientErrorException( HttpStatus.BAD_REQUEST, "error", FixturesLoader.getFixtures().get("AuthenticatePasswordInvalidCredentialsErrorResponse"), utf8 ) );}

StubLoginActivity@Rulepublic SpicedTestRule<LoginActivity> mTestRule = new SpicedTestRule<>(LoginActivity.class);

private void registerInvalidCredentialsLoginRequest() { Charset utf8 = Charset.forName("utf-8");

mTestRule.registerResponseFor( LoginRequest.class, new HttpClientErrorException( HttpStatus.BAD_REQUEST, "error", FixturesLoader.getFixtures().get("AuthenticatePasswordInvalidCredentialsErrorResponse"), utf8 ) );}

Functional test

@Testpublic void unauthorizedUserLogin() { registerInvalidCredentialsLoginRequest();

setUserAndPassword();

clickOnSignIn();

Context context = InstrumentationRegistry.getTargetContext();

String errorMessage = context.getString(R.string.InvalidCredentialsError);

onView(allOf(withText(errorMessage), withParent(withId(R.id.errors_wrapper)))). check(matches(isDisplayed()));}

LoginActivity

Functional test

@Testpublic void unauthorizedUserLogin() { registerInvalidCredentialsLoginRequest();

setUserAndPassword();

clickOnSignIn();

Context context = InstrumentationRegistry.getTargetContext();

String errorMessage = context.getString(R.string.InvalidCredentialsError);

onView(allOf(withText(errorMessage), withParent(withId(R.id.errors_wrapper)))). check(matches(isDisplayed()));}

LoginActivity

Espresso Usage

private UserSession mUserSession = CurrentUserSessionTestHelper.buildUserSession();

private void setUserAndPassword() { onView(ViewMatchers.withId(R.id.email)).perform(typeText(mUserSession.getEmail())); onView(withId(R.id.password)).perform(typeText("password"));}

private void clickOnSignIn() { onView(withId(R.id.action_login)).perform(click());}

LoginActivity

Instrumentation

@Testpublic void unauthorizedUserLogin() { registerInvalidCredentialsLoginRequest();

setUserAndPassword();

clickOnSignIn();

Context context = InstrumentationRegistry.getTargetContext();

String errorMessage = context.getString(R.string.InvalidCredentialsError);

onView(allOf(withText(errorMessage), withParent(withId(R.id.errors_wrapper)))). check(matches(isDisplayed()));}

LoginActivity

Instrumentation

@Testpublic void unauthorizedUserLogin() { registerInvalidCredentialsLoginRequest();

setUserAndPassword();

clickOnSignIn();

Context context = InstrumentationRegistry.getTargetContext();

String errorMessage = context.getString(R.string.InvalidCredentialsError);

onView(allOf(withText(errorMessage), withParent(withId(R.id.errors_wrapper)))). check(matches(isDisplayed()));}

LoginActivity

Espresso usage

@Testpublic void unauthorizedUserLogin() { registerInvalidCredentialsLoginRequest();

setUserAndPassword();

clickOnSignIn();

Context context = InstrumentationRegistry.getTargetContext();

String errorMessage = context.getString(R.string.InvalidCredentialsError);

onView(allOf(withText(errorMessage), withParent(withId(R.id.errors_wrapper)))). check(matches(isDisplayed()));}

LoginActivity

Espresso usage

onView(allOf(

withText(errorMessage), withParent(

withId(R.id.errors_wrapper))

) ).check(

matches(isDisplayed()

) );

LoginActivity

Espresso usage

onView(allOf(

withText(errorMessage), withParent(

withId(R.id.errors_wrapper))

) ).check(

matches(isDisplayed()

) );

LoginActivity

Espresso usage

onView(allOf(

withText(errorMessage), withParent(

withId(R.id.errors_wrapper))

) ).check(

matches(isDisplayed()

) );

LoginActivity

Espresso usage

onView(allOf(

withText(errorMessage), withParent(

withId(R.id.errors_wrapper))

) ).check(

matches(isDisplayed()

) );

LoginActivity

Espresso usage

onView(allOf(

withText(errorMessage), withParent(

withId(R.id.errors_wrapper))

) ).check(

matches(isDisplayed()

) );

LoginActivity

Espresso usage

onView(allOf(

withText(errorMessage), withParent(

withId(R.id.errors_wrapper))

) ).check(

matches(isDisplayed()

) );

LoginActivity

Espresso usage

onView(allOf(

withText(errorMessage), withParent(

withId(R.id.errors_wrapper))

) ).check(

matches(isDisplayed()

) );

LoginActivity

Espresso usage

onView(allOf(

withText(errorMessage), withParent(

withId(R.id.errors_wrapper))

) ).check(

matches(isDisplayed()

) );

LoginActivity

IntentsFunctional test - Landing Activity@Testpublic void testOnEnterAction() { Intent result = new Intent();

intending(hasComponent(hasClassName(equalTo(LoginActivity.class.getName())))). respondWith( new Instrumentation.ActivityResult(Activity.RESULT_OK, result) );

intending(hasComponent(hasClassName(equalTo(HomeActivity.class.getName())))). respondWith( new Instrumentation.ActivityResult(Activity.RESULT_CANCELED, null) );

clickOn(R.id.action_enter);

intended(hasComponent(hasClassName(equalTo(HomeActivity.class.getName()))));}

IntentsFunctional test - Landing Activity@Testpublic void testOnEnterAction() { Intent result = new Intent();

intending(hasComponent(hasClassName(equalTo(LoginActivity.class.getName())))). respondWith( new Instrumentation.ActivityResult(Activity.RESULT_OK, result) );

intending(hasComponent(hasClassName(equalTo(HomeActivity.class.getName())))). respondWith( new Instrumentation.ActivityResult(Activity.RESULT_CANCELED, null) );

clickOn(R.id.action_enter);

intended(hasComponent(hasClassName(equalTo(HomeActivity.class.getName()))));}

IntentsFunctional test - Landing Activity@Testpublic void testOnEnterAction() { Intent result = new Intent();

intending(hasComponent(hasClassName(equalTo(LoginActivity.class.getName())))). respondWith( new Instrumentation.ActivityResult(Activity.RESULT_OK, result) );

intending(hasComponent(hasClassName(equalTo(HomeActivity.class.getName())))). respondWith( new Instrumentation.ActivityResult(Activity.RESULT_CANCELED, null) );

clickOn(R.id.action_enter);

intended(hasComponent(hasClassName(equalTo(HomeActivity.class.getName()))));}

IntentsFunctional test - Landing Activity@Testpublic void testOnEnterAction() { Intent result = new Intent();

intending(hasComponent(hasClassName(equalTo(LoginActivity.class.getName())))). respondWith( new Instrumentation.ActivityResult(Activity.RESULT_OK, result) );

intending(hasComponent(hasClassName(equalTo(HomeActivity.class.getName())))). respondWith( new Instrumentation.ActivityResult(Activity.RESULT_CANCELED, null) );

clickOn(R.id.action_enter);

intended(hasComponent(hasClassName(equalTo(HomeActivity.class.getName()))));}

IntentsFunctional test - Landing Activity@Testpublic void testOnEnterAction() { Intent result = new Intent();

intending(hasComponent(hasClassName(equalTo(LoginActivity.class.getName())))). respondWith( new Instrumentation.ActivityResult(Activity.RESULT_OK, result) );

intending(hasComponent(hasClassName(equalTo(HomeActivity.class.getName())))). respondWith( new Instrumentation.ActivityResult(Activity.RESULT_CANCELED, null) );

clickOn(R.id.action_enter);

intended(hasComponent(hasClassName(equalTo(HomeActivity.class.getName()))));}

Android Components

Utilities

Android system

UI

Stubs

Android Components

Utilities

Android system

UI

StubsUnit testing

● jUnit ● Instrumentation

Android Components

Utilities

Android system

UI

Stubs

Using manual or library-assisted dependency injection to switch dependencies for stubs when testing

Unit testing

● jUnit ● Instrumentation

Android Components

Utilities

Android system

UI

Stubs

Espresso to manipulate and unit-test views in an isolated Activity

Using manual or library-assisted dependency injection to switch dependencies for stubs when testing

Unit testing

● jUnit ● Instrumentation

Android Components

Utilities

Android system

UI

Stubs

Espresso to manipulate and unit-test views in an isolated Activity

UI automator

Functional test of interactions between the app and the Android system

Using manual or library-assisted dependency injection to switch dependencies for stubs when testing

Unit testing

● jUnit ● Instrumentation

Android Components

Utilities

Android system

UI

Stubs

Espresso to manipulate and unit-test views in an isolated Activity

Functional testing

● Espresso● Espresso Intents● Instrumentation● Stubs

UI automator

Functional test of interactions between the app and the Android system

Using manual or library-assisted dependency injection to switch dependencies for stubs when testing

Unit testing

● jUnit ● Instrumentation

...then 2 + 2 = 4 so...We can test on Android!

Thanks toAriel, Gian, Juanma, Michel

@moove_it

Questions?