본문

180501(수) - Test UI for a single app

Test UI for a single app

- single app에서 user interaction test를 하면 사용자가 unexpected results or poor experience 를 겪지 않도록 할 수 있다.

- app UI가 올바르게 작동하는지 확인하려면 UI test를 만드는 습관이 필요하다.


- Android Testing support Library에서 제공하는 Espresso testing framework는 single target app에서의 user interactions를  simulate하기위한 API를 제공한다.

- Espresso tests는 Android 2.3.3(API level 10) and higher에서 running

- Espresso를 사용하면 testing중인 app의 UI와 test actions을 automatic synchronization할 수 있다는 benefit이 있다.


- Espresso는 main thread가 idle상태일 때 이를 detects하고, 적절한 time에 test commands를 run하여 test reliability를 향상시킨다.

- 이러한 capability를 사용하면 test code에 Thread.sleep()과 같은 timing workarounds를 add할 필요가 없다.


- Espresso test framework는 instrumentation-based API and AndroidJUnitRunnerworks test runner


Set up Espresso

- make sure to configure test source code location and project dependencies


dependencies {
   
// Other dependencies ...
    androidTestImplementation
'com.android.support.test.espresso:espresso-core:3.0.2'
}

dependencies {
   
// Other dependencies ...
    androidTestImplementation
'com.android.support.test.espresso:espresso-core:3.0.2'
}
dependencies {
   
// Other dependencies ...
    androidTestImplementation
'com.android.support.test.espresso:espresso-core:3.0.2'
}

- test device의 animation을 꺼야한다.

- unexpected results or may test to fail

- Developer options 에서 아래 옵션을 전부 off

1. Window animation scale

2. Transition animation scale

3. Animator duration scale


- more info resource.


Create an Espresso test class

1. onView() or AdapterView onData() method calling하여 Activity에서 test하려는 UI component find

2. ViewInteraction.perform() or DataInteraction.perform()method calling and passing the user action (ex. click on the sign-in button)

- 같은 UI component에서 multiple actions sequence를 지정하려면 comma-separated list in method argument

3. 위 steps를 repeat하여 target app에서의 user flow across multiple activities를 simulate한다.

4. 이런 user interactions가 수행된 후에 ViewAssertions methods를 사용해서 expected state or behavior UI reflects가 되었는지 확인한다.

onView(withId(R.id.my_view))            // withId(R.id.my_view) is a ViewMatcher
       
.perform(click())               // click() is a ViewAction
       
.check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion


Use Espresso with ActivityTestRule

- JUnit4 style로 Espresso test를 작성하고, ActivityTestRule을 사용해서 reduce amount of boilerplate code하는 법을 설명

- ActivityTestRule testing framework는 @Test annotation을 추가한 test method와 @Before annotation을 붙인 method before activity under test.

- framework는 test가 끝난 후 activity를 shutting down하고 @After annotation이 붙은 all method가 실행되도록 한다.

package com.example.android.testing.espresso.BasicSample;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
...

@RunWith(AndroidJUnit4.class)
@LargeTest
public class ChangeTextBehaviorTest {

   
private String mStringToBetyped;

   
@Rule
   
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
           
MainActivity.class);

   
@Before
   
public void initValidString() {
       
// Specify a valid string.
        mStringToBetyped
= "Espresso";
   
}

   
@Test
   
public void changeText_sameActivity() {
       
// Type text and then press the button.
        onView
(withId(R.id.editTextUserInput))
               
.perform(typeText(mStringToBetyped), closeSoftKeyboard());
        onView
(withId(R.id.changeTextBt)).perform(click());

       
// Check that the text was changed.
        onView
(withId(R.id.textToBeChanged))
               
.check(matches(withText(mStringToBetyped)));
   
}
}


Access UI components

- Espresso가 testing app과 interaction하려면 먼저 UI component or view를 지정해야 한다.

- Espresso supports Hamcrest matchers for specifying views and adapters

- find the view하려면, onView() method를 call and pass view matcher the targeting specifies view

- OnView() method는 test가 view와 interact할 수 있도록 하는 ViewInteraction object를 return


- RecyclerView layout에서 onView method를 call하면 not work 할 수 있다.

- 이러한 경우  Locating a view in an AdapterView 참조


※ onView() method는 view valid를 check하지 않는다. 

- 대신 Espresso는 matcher provided를 사용해서 only view hierarchy searches.

- match하지 않으면 method는 NoMatchingViewException.를 throw

public void testChangeText_sameActivity() {
   
// Type text and then press the button.
    onView
(withId(R.id.editTextUserInput))
           
.perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard());
    onView
(withId(R.id.changeTextButton)).perform(click());

   
// Check that the text was changed.
   
...
}


Specify a view matcher

- calling method in  ViewMatchers class

- ex) displays text string을 보고 find view

- Android resource IDs는 not unique이므로 match ID가 more than one view이면 AmbiguousViewMatcherException throw

onView(withText("Sign-in"));
onView(withId(R.id.button_signin));

- using hamcrest  Matchers class

- allOf() methods combine multiple matchers

- not도 사용가능

Hamcrest site.

onView(allOf(withId(R.id.button_signin), withText("Sign-in")));
onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))));

- Espresso test의 performance를 향상시키려면 matching을 minimum해라.


Locate a view in an AdapterView

- AdapterView widget에서 view는 runtime에 child views로 dynamically하게 채워진다.

- target view가  AdapterView 내부에 있는경우 loaded in the current view hierarch이므로 onView() method가 작동하지 않을 수 있다.

- instead call onData() method, obtain DataInteraction object to access the target view element

- Espresso also take care scrolling to target element and putting the element into focus


※ onData() method도 마찬가지로 not check specified corresponds view.

- Espresso는 current view hierarch만 search.

- if no match, throw NoMatchingViewException.

onData(allOf(is(instanceOf(Map.class)),
        hasEntry
(equalTo(LongListActivity.ROW_TEXT), is("test input")));


Perform actions

ViewInteraction.perform() or DataInteraction.perform() methods를 calling해서 UI component의 user interactions simulate

- one or more ViewActionobjects를 arguments로 pass해야한다.

- Espresso는 sequence according order에 따라서 action! and executes in main thread

-  ViewAction class는  common actions  helper methods를 제공

- individual ViewAction object를 creating  하는 대신에 these methods as convenient shortcuts

- target view가 ScrollView 안에 있으면, ViewActions.scrollTo() action을 먼저 수행하여 다른 action을 진행하기 전에 first to display the view

- view가 이미 displayed이면, no effect


Test activities in isolation with Espresso Intents

Espresso Intents는 app에서 send한 intents의 validation and stubbing 을 enables한다.

- outgoing intents를 intercepting and subbing 하고 testing중인 component로 다시 send하여 app, activity, or service를 isolation하여 테스트 가능하다.

dependencies {
 
// Other dependencies ...
  androidTestCompile
'com.android.support.test.espresso:espresso-intents:2.2.2'
}

- intent를 테스트하려면 ActivityTestRule class와 similar한 IntentTestRule class instance를 만들어야 한다.

- IntentsTestRule class 는 test전에 Espresso Intents를 initializes, terminates host activity, and releases Espresso Intents after each test

@Large
@RunWith(AndroidJUnit4.class)
public class SimpleIntentTest {

   
private static final String MESSAGE = "This is a test";
   
private static final String PACKAGE_NAME = "com.example.myfirstapp";

   
/* Instantiate an IntentsTestRule object. */
   
@Rule
   
public IntentsTestRule<MainActivity> mIntentsRule =
           
new IntentsTestRule<>(MainActivity.class);

   
@Test
   
public void verifyMessageSentToMessageActivity() {

       
// Types a message into a EditText element.
        onView
(withId(R.id.edit_message))
               
.perform(typeText(MESSAGE), closeSoftKeyboard());

       
// Clicks a button to send the message to another
       
// activity through an explicit intent.
        onView
(withId(R.id.send_message)).perform(click());

       
// Verifies that the DisplayMessageActivity received an intent
       
// with the correct package name and message.
        intended
(allOf(
                hasComponent
(hasShortClassName(".DisplayMessageActivity")),
                toPackage
(PACKAGE_NAME),
                hasExtra
(MainActivity.EXTRA_MESSAGE, MESSAGE)));

   
}
}


Test WebViews with Espresso Web

WebDriver API 를 사용해서 inspect and control behavior of WebView

dependencies {
 
// Other dependencies ...
  androidTestImplementation
'com.android.support.test.espresso:espresso-web:3.0.2'
}

- WebView에서 JavaScript를 enable해야 한다.

@LargeTest
@RunWith(AndroidJUnit4.class)
public class WebViewActivityTest {

   
private static final String MACCHIATO = "Macchiato";
   
private static final String DOPPIO = "Doppio";

   
@Rule
   
public ActivityTestRule<WebViewActivity> mActivityRule =
       
new ActivityTestRule<WebViewActivity>(WebViewActivity.class,
           
false /* Initial touch mode */, false /*  launch activity */) {

       
@Override
       
protected void afterActivityLaunched() {
           
// Enable JavaScript.
            onWebView
().forceJavascriptEnabled();
       
}
   
}

   
@Test
   
public void typeTextInInput_clickButton_SubmitsForm() {
       
// Lazily launch the Activity with a custom start Intent per test
       mActivityRule
.launchActivity(withWebFormIntent());

       
// Selects the WebView in your layout.
       
// If you have multiple WebViews you can also use a
       
// matcher to select a given WebView, onWebView(withId(R.id.web_view)).
       onWebView
()
           
// Find the input element by ID
           
.withElement(findElement(Locator.ID, "text_input"))
           
// Clear previous input
           
.perform(clearElement())
           
// Enter text into the input element
           
.perform(DriverAtoms.webKeys(MACCHIATO))
           
// Find the submit button
           
.withElement(findElement(Locator.ID, "submitBtn"))
           
// Simulate a click via JavaScript
           
.perform(webClick())
           
// Find the response element by ID
           
.withElement(findElement(Locator.ID, "response"))
           
// Verify that the response page contains the entered text
           
.check(webMatches(getText(), containsString(MACCHIATO)));
   
}
}


Verify results

ViewInteraction.check() or DataInteraction.check() method to assert UI matches some expected state

- must pass in a ViewAssertion object as the argument

- assertion fails, throws an AssertionFailedError.

      • doesNotExist: Asserts that there is no view matching the specified criteria in the current view hierarchy.
      • matches: Asserts that the specified view exists in the current view hierarchy and its state matches some given Hamcrest matcher.
      • selectedDescendentsMatch: Asserts that the specified children views for a parent view exist, and their state matches some given Hamcrest matcher.
public void testChangeText_sameActivity() {
   
// Type text and then press the button.
   
...

   
// Check that the text was changed.
    onView
(withId(R.id.textToBeChanged))
           
.check(matches(withText(STRING_TO_BE_TYPED)));
}


Run Espresso tests on a device or emulator

- Android Studio or command-line에서 Espresso test를 run 가능

- project에서 AdrnoidJUnitRunner를 default instrumentation runner로 지정해야 한다.

Getting Started with Testing.

Espresso API Reference.

Android Testing Codelab.

Espresso Code Samples 

공유

댓글