Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Welcome to Android World!

Slide 3

Slide 3 text

Application is growing

Slide 4

Slide 4 text

Application is growing

Slide 5

Slide 5 text

Categories of Android tests Run on the JVM (local tests) Require Android device (instrumentation tests) Tests

Slide 6

Slide 6 text

How to test the project?

Slide 7

Slide 7 text

Unit tests JUnit Hamcrest Mockito Robolectric

Slide 8

Slide 8 text

Integration tests JUnit Hamcrest Mockito Instrumentation API

Slide 9

Slide 9 text

Acceptance tests Espresso UiAutomator Robotium Calabash Appium

Slide 10

Slide 10 text

Android Testing Support Library AndroidJUnitRunner JUnit4 Rules Espresso UiAutomator

Slide 11

Slide 11 text

AndroidJUnitRunner ü JUnit4 support ü Instrumentation Registry ü Activity and Application life-cycle monitoring

Slide 12

Slide 12 text

JUnit4 Rules ü ActivityTestRule ü IntentsTestRule ü ServiceTestRule ü ProviderTestRule ü GrantPermissionRule (API 23 – Android Marshmallow)

Slide 13

Slide 13 text

Espresso Android 2.2 (API 8) Turn off the animations

Slide 14

Slide 14 text

Espresso onView(ViewMatcher) .perform(ViewAction) .check(ViewAssertion)

Slide 15

Slide 15 text

Get View by text Matcher Action Assertion onView(withText("Pay"))

Slide 16

Slide 16 text

Get View by text Matcher Action Assertion onView(withText(R.string.pay))

Slide 17

Slide 17 text

Get View by id Matcher Action Assertion onView(withId(R.id.pay))

Slide 18

Slide 18 text

Click to view Matcher Action Assertion onView(withId(R.id.pay)) .perform(click())

Slide 19

Slide 19 text

Long click to view Matcher Action Assertion onView(withId(R.id.pay)) .perform(longClick())

Slide 20

Slide 20 text

Double click to view Matcher Action Assertion onView(withId(R.id.pay)) .perform(doubleClick())

Slide 21

Slide 21 text

Enter a text Matcher Action Assertion onView(withId(R.id.payment)) .perform(typeText("120$"))

Slide 22

Slide 22 text

Close a keyboard Matcher Action Assertion onView(withId(R.id.payment)) .perform(closeSoftKeyboard())

Slide 23

Slide 23 text

Enter a text, close a keyboard Matcher Action Assertion onView(withId(R.id.payment)) .perform(typeText("120$"), closeSoftKeyboard())

Slide 24

Slide 24 text

Check the hint Matcher Action Assertion onView(withId(R.id.payment)) .check(matches( withHint(R.string.payment_hint) ))

Slide 25

Slide 25 text

Check the text Matcher Action Assertion onView(withId(R.id.payment)) .check(matches( withText(R.string.payment)))

Slide 26

Slide 26 text

Check the visibility Matcher Action Assertion onView(withId(R.id.payment)) .check(matches( isDisplayed()))

Slide 27

Slide 27 text

onView(withId(R.id.payment)) .perform(typeText("120$"), closeSoftKeyboard()) onView(withId(R.id.pay_button)) .check(matches(isDisplayed())) .perform(click())

Slide 28

Slide 28 text

onView(withId(R.id.payment)) .perform(typeText("120$"), closeSoftKeyboard()) onView(withId(R.id.pay_button)) .check(matches(isDisplayed())) .perform(click())

Slide 29

Slide 29 text

Hamcrest matchers

Slide 30

Slide 30 text

onView(withId(R.id.payment)) .check(matches( `is` withText(startsWith("Go")))) endsWith

Slide 31

Slide 31 text

@RunWith(AndroidJUnit4::class) class LoginActivityTest { @Rule @JvmField val mActivityRule = ActivityTestRule(LoginActivity::class.java) @Test fun shouldVerifyEmptyPassword() { onView(withId(R.id.edit_text_username)) .perform(typeText("Alex"), closeSoftKeyboard()) onView(withId(R.id.login_button)) .perform(click()) onView(withId(R.id.password_text_layout)) .check(matches( withError(`is`("Please enter a password")))) } }

Slide 32

Slide 32 text

@RunWith(AndroidJUnit4::class) class LoginActivityTest { @Rule @JvmField val mActivityRule = ActivityTestRule (LoginActivity::class.java) @Test fun shouldVerifyEmptyPassword() { onView(withId(R.id.edit_text_username)) .perform(typeText("Alex"), closeSoftKeyboard()) onView(withId(R.id.login_button)) .perform(click()) onView(withId(R.id.password_text_layout)) .check(matches( withError(`is`("Please enter a password")))) } }

Slide 33

Slide 33 text

@RunWith(AndroidJUnit4::class) class LoginActivityTest { @Rule @JvmField val mActivityRule = ActivityTestRule(LoginActivity::class.java) @Test fun shouldVerifyEmptyPassword() { onView(withId(R.id.edit_text_username)) .perform(typeText("Alex"), closeSoftKeyboard()) onView(withId(R.id.login_button)) .perform(click()) onView(withId(R.id.password_text_layout)) .check(matches( withError(`is`("Please enter a password")))) } }

Slide 34

Slide 34 text

@RunWith(AndroidJUnit4::class) class LoginActivityTest { @Rule @JvmField val mActivityRule = ActivityTestRule(LoginActivity::class.java) @Test fun shouldVerifyEmptyPassword() { onView(withId(R.id.edit_text_username)) .perform(typeText("Alex"), closeSoftKeyboard()) onView(withId(R.id.login_button)) .perform(click()) onView(withId(R.id.password_text_layout)) .check(matches( withError(`is`("Please enter a password")))) } }

Slide 35

Slide 35 text

@RunWith(AndroidJUnit4::class) class LoginActivityTest { @Rule @JvmField val mActivityRule = ActivityTestRule(LoginActivity::class.java) @Test fun shouldVerifyEmptyPassword() { onView(withId(R.id.edit_text_username)) .perform(typeText("Alex"), closeSoftKeyboard()) onView(withId(R.id.login_button)) .perform(click()) onView(withId(R.id.password_text_layout)) .check(matches( withError(`is`("Please enter a password")))) } }

Slide 36

Slide 36 text

@RunWith(AndroidJUnit4::class) class LoginActivityTest { @Rule @JvmField val mActivityRule = ActivityTestRule(LoginActivity::class.java) @Test fun shouldVerifyEmptyPassword() { onView(withId(R.id.edit_text_username)) .perform(typeText("Alex"), closeSoftKeyboard()) onView(withId(R.id.login_button)) .perform(click()) onView(withId(R.id.password_text_layout)) .check(matches(withError(`is`( "Please enter a password")))) } } * withError – a custom matcher

Slide 37

Slide 37 text

onView(withId(R.id.download)) .perform(click()) onView(withId(android.support.design.R.id.snackbar_text)) .check(matches( withText(R.string.downloading_movie_info)))

Slide 38

Slide 38 text

What is an AdapterView and how it works? java.lang.Object ↳ android.view.View ↳ android.view.ViewGroup ↳ android.widget.AdapterView GridView ListView Spinner

Slide 39

Slide 39 text

Espresso onData(ObjectMatcher) .DataOptions .perform(ViewAction) .check(ViewAssertion)

Slide 40

Slide 40 text

onData – ArrayAdapter onData(allOf(`is`(instanceOf(String::class.java)), `is`("Americano"))) .check(matches(isDisplayed())) val arrayAdapter = ArrayAdapter( this@DetailActivity, android.R.layout.simple_list_item_1, android.R.id.text1, coffeeList)

Slide 41

Slide 41 text

onData – custom adapter @Test fun clickCoffeeItem() { ... onData(withCoffee(espresso)) .inAdapterView(withId(R.id.detail_list)) .perform(click()); } class CoffeeAdapter : BaseAdapter() { ... override fun getItem(position: Int): Any { return mOrderedCoffee.keys.toTypedArray()[position] } }

Slide 42

Slide 42 text

fun withCoffee(expected: Coffee) : Matcher { return object : BoundedMatcher(Coffee::class.java) { override fun describeTo(description: Description) { description.appendText("has value: $expected") } override fun matchesSafely(item: Coffee): Boolean { return item == expected } } }

Slide 43

Slide 43 text

RecyclerView java.lang.Object ↳ android.view.View ↳ android.view.ViewGroup ↳ android.support.v7.widget.RecyclerView It is not an AdapterView

Slide 44

Slide 44 text

RecyclerViewActions ü actionOnHolderItem ü actionOnItem ü actionOnItemAtPosition ü scrollTo ü scrollToHolder ü scrollToPosition onView(withId(R.id.recyclerView)) .perform(actionOnItemAtPosition(3, click()));

Slide 45

Slide 45 text

IdlingResource class ServiceIdlingResource : IdlingResource { lateinit var mResourceCallback : ResourceCallback override fun getName(): String { return ServiceIdlingResource::class.simpleName!! } override fun isIdleNow(): Boolean { val isIdle = !isIntentServiceRunning() if (isIdle) { mResourceCallback.onTransitionToIdle() } return isIdle } override fun registerIdleTransitionCallback(callback: ResourceCallback) { mResourceCallback = callback } fun isIntentServiceRunning() : Boolean { ... } } http://blog.sqisland.com/2015/04/espresso-custom-idling-resource.html

Slide 46

Slide 46 text

Using IdlingResource @Before fun setUp() { mLoadingIdlingResource = ServiceIdlingResource() IdlingRegistry.getInstance().register(mLoadingIdlingResource) } @Test fun shouldVerifyMessageDuringDownloadingMoviesInfo() { onView(withId(R.id.fab)) .perform(click()) onView(withId(android.support.design.R.id.snackbar_text)) .check(matches(withText(R.string.downloading_movie_info))) } @After fun tearDown() { IdlingRegistry.getInstance().unregister(mLoadingIdlingResource) }

Slide 47

Slide 47 text

CountingIdlingResource class MovieDetailActivity : AppCompatActivity() { val mCountingIdlingResource = CountingIdlingResource(LOADING_MOVIE_DETAILS) companion object { private const val LOADING_MOVIE_DETAILS = "loading_movie_details" } fun loadMore() { ... mCountingIdlingResource.increment() } fun showMovies() { ... mCountingIdlingResource.decrement() } }

Slide 48

Slide 48 text

Using CountingIdlingResource @Before fun setUp() { mLoadingIdlingResource = mMovieDetailActivityRule. activity.mCountingIdlingResource IdlingRegistry.getInstance().register(mLoadingIdlingResource) } @Test fun shouldVerifyMessageDuringDownloadingMoviesInfo() { onView(withId(R.id.fab)) .perform(click()) onView(withId(android.support.design.R.id.snackbar_text)) .check(matches(withText(R.string.downloading_movie_info))) } @After fun tearDown() { IdlingRegistry.getInstance().unregister(mLoadingIdlingResource) }

Slide 49

Slide 49 text

Configure a custom intent @Rule @JvmField val mMovieDetailActivityRule = ActivityTestRule( MovieDetailActivity::class.java, true, false) // is activity launch? @Test fun `Should display message during downloading movie info`() { val context = InstrumentationRegistry.getTargetContext() val movieIntent = Intent(context, MovieDetailActivity::class.java) movieIntent.putExtra("movie_id", ANNABELLE_MOVIE) mMovieDetailActivityRule.launchActivity(movieIntent) ... } App data: https://www.themoviedb.org

Slide 50

Slide 50 text

Handle animation inside the app val animatorSpeed = Settings.Global.getFloat(​ contentResolver,​ SETTINGS_NAME,​ 0.0f)​ ​ ü Settings.Global.ANIMATOR_DURATION_SCALE ​ ü Settings.Global.TRANSITION_ANIMATION_SCALE​ ü Settings.Global.WINDOW_ANIMATION_SCALE​

Slide 51

Slide 51 text

UI AutomatorViewer Android Studio Tools / Android / Android Device Monitor Application {ANDROID_SDK} / tools / uiautomatorviewer

Slide 52

Slide 52 text

UiAutomator Android 4.3 (API 18) Interacting with any application Interacting with a system

Slide 53

Slide 53 text

Basic UI Automator components ü UiDevice ü UiCollection ü UiObject / UiObject2 ü UiScrollable ü UiSelector ü Configurator

Slide 54

Slide 54 text

UiDevice ü Press "Home" button mUiDevice.pressHome() ü Press "Back" button mUiDevice.pressBack() ü Press “Power” button mUiDevice.wakeUp() ü Click at arbitrary coordinates mUiDevice.click(300, 215) ü Open the notifications mUiDevice.openNotification()

Slide 55

Slide 55 text

Test of calculator application class CalculatorAppTest { lateinit var mDevice: UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }

Slide 56

Slide 56 text

Test of calculator application class CalculatorAppTest { lateinit var mDevice: UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }

Slide 57

Slide 57 text

Test of calculator application class CalculatorAppTest { lateinit var mDevice: UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }

Slide 58

Slide 58 text

Test of calculator application class CalculatorAppTest { lateinit var mDevice: UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }

Slide 59

Slide 59 text

Test of calculator application class CalculatorAppTest { lateinit var mDevice: UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }

Slide 60

Slide 60 text

Test of calculator application class CalculatorAppTest { lateinit var mDevice: UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }

Slide 61

Slide 61 text

Test of calculator application class CalculatorAppTest { lateinit var mDevice: UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }

Slide 62

Slide 62 text

Test of calculator application class CalculatorAppTest { lateinit var mDevice: UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }

Slide 63

Slide 63 text

Espresso + UiAutomator Interacting with a system Better interaction with the app

Slide 64

Slide 64 text

@RunWith(AndroidJUnit4::class.java) class FullOrderTest { ... @Test fun shouldOrderThreeEspressos() { ... onView(withId(R.id.pay)).perform(click()) mDevice.openNotification() mDevice.wait(Until.hasObject( By.text(NOTIFICATION_TITLE)), TIMEOUT) val title = mDevice.findObject( By.text(NOTIFICATION_TITLE)) assertEquals(NOTIFICATION_TITLE, title.getText()) ... } }

Slide 65

Slide 65 text

@RunWith(AndroidJUnit4::class.java) class FullOrderTest { ... @Test fun shouldOrderThreeEspressos() { ... onView(withId(R.id.pay)).perform(click()) mDevice.openNotification() mDevice.wait(Until.hasObject( By.text(NOTIFICATION_TITLE)), TIMEOUT) val title = mDevice.findObject( By.text(NOTIFICATION_TITLE)) assertEquals(NOTIFICATION_TITLE, title.getText()) ... } }

Slide 66

Slide 66 text

Q & A 1. Testing Support library https://developer.android.com/topic/libraries/testing-support-library/ 2. Android Testing Codelab https://codelabs.developers.google.com/codelabs/android-testing/ 3. Espresso https://developer.android.com/training/testing/espresso/ 4. UiAutomator https://developer.android.com/training/testing/ui-automator.html 5. Blog http://alexzh.com/