본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[android] Espresso Tutorial

by 돼지왕왕돼지 2019. 2. 4.
반응형

[android] Espresso Tutorial


http://www.vogella.com/tutorials/AndroidTestingEspresso/article.html


:app, activity test, activitylifecyclemonitorregistry, activityrule, ActivityTestRule, ActivityTestRule param, AdapterView, allof, Android Support Repository, android testing framework, androidtestcompile, animation off, animator duration scale, app test, async job test, asynchronoustask, black box testing, CHECK, clearText, Click, configuremainactivity, connectedcheck, containsstring, core intents web, countdownlatch, custom matcher, datainteraction object, developer options, device settings, doesnotexist, espresso activity test, espresso androidtestcompile, espresso config, espresso configuration, espresso dependency, espresso gradle, espresso install, espresso matcher, espresso permission, espresso static import, espresso test framework, espresso toast, event queue, getactivitiesinstage, getactivity, getinstrumentation, gettargetcontext, hamcrest matcher, hasextra, idlingresource, inroot, Installation, instanceof, instrumentation api, InstrumentationRegistry, intended, intent config, intent mocking, intentservice, intentstestrule, isdisplayed, Matches, monitor, ondata, onview, performing actions, presskey, record espresso test, registeridlingresources, resumed, rule.launchactivity, run espresso test, run test, thread pool, topackage, transition animation scale, TypeText, ui recorder, ui test, using gradle, verification, verifying test results, ViewActions, ViewAssertions, viewinteraction object, viewinteraction.check, viewmatcher, ViewMatchers, window animation scale, withdecorview, withId, withText, [android] Espresso Tutorial



1. The Espresso test framework


-

Android testing framework 이며, UI test 를 쉽게 해준다.

Google 이 2013년 10월에 첫 release 를 했고, 글 쓰는 시점에 2.0 이상이 release 되었으며 계속 유지보수 되고 있다.



-

Test 를 수행하기 전에 Activity 가 실행되었음을 보장한다.



-

single app test 가 주되지만, app 간의 테스트도 가능하다.

너의 app 바깥쪽의 일을 테스트하는 것은 black box testing 만 가능하다.



-

Espresso 는 아래 3개의. component 를 갖는다.


ViewMatchers - view 를 찾는다.

ViewActions - view 에 action 을 수행할 수 있다.

ViewAssertions - view 상태에 대한 assert 를 수행



-

아래는 espresso 예제 코드

onView(withId(R.id.my_view))
    .perform(click())
    .check(matches(isDisplayed()))

onView(withId(R.id.green_button))
    .perform(click())
    .check(matches(not(isEnabled()));





2. Making Espresso available


2.1. Installation


-

Android SDK manager 를 통해서 Android Support Repository 를 설치




2.2. Configuration of the Gradle build file for Espresso


-

// build.gradle

dependencies {

    compile fileTree(dir: 'libs', include: ['*.jar'])


    testCompile 'junit:junit:4.12'


    // Android runner and rules support

    androidTestCompile 'com.android.support.test:runner:0.5'

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


    // Espresso support

    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {

        exclude group: 'com.android.support', module: 'support-annotations'

    })


    // add this for intent mocking support

    androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'


    // add this for webview testing support

    androidTestCompile 'com.android.support.test.espresso:espresso-web:2.2.2'

}


android {

    defaultConfig {

        ...

        testInstrumentationRunner “android.support.test.runner.AndroidJUnitRunner"

    }

    ...

}




2.3. Device settings


-

testing 시에는 혼란을 줄 수 있기 때문에 animation 을 off 시키는 것이 좋다.


Developer options(개발자 메뉴) 의 Window animation scale, Transition animation scale, Animator duration scale 을 모두 1x 로 한다.





3. Exercise: A first Espresso test


3.1. Create project under test




3.2. Adjust the app build.gradle




3.3. Create your Espresso test


-

@RunWith(AndroidJUnit4.class)
public class MainActivityEspressoTest{
    @Rule
    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);

    @Test
    public void ensureTextChangesWotk(){
        onView(withId(R.id.inputField))
            .perform(typeText(“HELLO”), closeSoftKeyboard());
        onView(withId(R.id.changeText)).perform(click());
        onView(withId(R.id.inputField)).check(matchers(withText(“Lalala”)));
    }

    @Test
    public void changeText_newActivity(){
        onView(withId(R.id.inputField)).perform(typeText(“NewText”), closeSoftKeyboard());
        onView(withId(R.id.resultView)).check(matches(withText(“NewText”)));
    }
}




3.4. Run your test






4. More on writing Espresso unit tests


4.1. Location of Espresso tests and required static imports


-

app/src/androidTest folder 밑에 test 코드가 존재한다.

Espresso API 를 간단히 하기 위해 다음과 같이 static import 를 사용할 것이 추천된다.

import static android.support.test.espresso.Espresso.onView;

import static android.support.test.espresso.action.ViewActions.click;

import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;

import static android.support.test.espresso.action.ViewActions.typeText;

import static android.support.test.espresso.assertion.ViewAssertions.matches;

import static android.support.test.espresso.matcher.ViewMatchers.withId;

import static android.support.test.espresso.matcher.ViewMatchers.withText;




4.2. Using ViewMatcher


-

View 를 찾기 위해서는 onView() method 를 사용한다.

AdpaterView 를 사용한다면 onData() 를 사용하면 된다.


onView() method 는 ViewInteraction object 를 return 한다.

onData() 는 DataInteraction object 를 return 한다.



-

onView 와 onData 안에는 espresso matcher 가 들어간다.

withText(“SOMETEXT”)

    SOMETEXT 를 text 로 가진 view 를 찾는다.


withId(R.id.inputField)

    특정 id 를 가진 view 를 찾는다.



Hamcrest Matchers

    containsString, instaceOf 등을 사용한 custom matcher 를 만들 수 있다.

    또한 다음과 같이 allOf 를 사용해 matcher 조합을 만들 수도 있다.

onView(allOf(withId(R.id.button_login), not(withText(“Logout “))));




4.3. Performing Actions


-

ViewInteraction 과 DataInteraction 은 ViewAction 을 통해 특정 action 을 수행할 수 있다.

ViewActions.click()

ViewActions.typeText()

ViewActions.pressKey()

ViewActions.clearText()

위 함수들은 다시 ViewInteraction 이나 DataInteraction 을 return 한다.




4.4. Verifying test results


-

ViewInteraction.check() 를 통해 assert 를 수행할 수 있다.

Hamcrest matcher 를 사용한 matches 나 doesNotExist 등을 통해 assert 를 수행한다.




4.5. Access to the instrumentation API


-

InstrumentationRegistry.getTargetContext() 를 통해서 app 의 target context 를 가져올 수 있다.




4.6. Configuring the start intent for the activity


-

ActivityTestRule 의 3번째 파라미터로 false 를 전달하면, activity 를 start 하는 intent 를 설정할 수 있다.



-

@Test
public void demonstrateIntentPrep() {
    Intent intent = new Intent();
    intent.putExtra("EXTRA", "Test");
    rule.launchActivity(intent);
    onView(withId(R.id.display)).check(matches(withText("Test")));
}




4.7. Adapter views


-

onData(allOf(is(instanceOf(sString.class)), is(“Eclipse”))).perform(click());


onData(allOf(is(instanceOf(Map.class)), hasEntry(equalTo(“STR”), is(“item:50”))).perform(click());


onData(withItemContent(“item: 60”)).onChildView(withId(R.id.item_size)).perform(click());




4.8. Espresso testing with permissions


-

@Before
public void grantPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        getInstrumentation().getUiAutomation().executeShellCommand("grant “ + getTargetContext().getPackageName() + " android.permission.CALL_PHONE");
    }
}




4.9. Espresso UI recorder


-

Android Studio 는 [Run] 메뉴 아래 "Record Espresso Test” menu 를 제공한다.




4.10. Configuring the activity under test


-

@Test public void useAppContext() throws Exception { MainActivity activity = mActivityRule.getActivity(); activity.configureMainActivity("http://www.vogella.com"); // configure.. function 은 MainActivity 안의 함수 // do more }



-

다음과 같이 현재 activity 를 가져올 수도 있다.

@Test
public void navigate() {
    Activity instance = getActivityInstance();
    onView(withText("Next")).perform(click());
    Activity activity = getActivityInstance();
    boolean b = (activity instanceof  SecondActivity);
    assertTrue(b);
    // do more
}

public Activity getActivityInstance() { 
    final Activity[] activity = new Activity[1];
    InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable( ) {
        public void run() {
            Activity currentActivity = null;
            Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED);
            if (resumedActivities.iterator().hasNext()){
                currentActivity = (Activity) resumedActivities.iterator().next();
                activity[0] = currentActivity;
            }
        }
    });
    return activity[0];
}




4.11. Running Espresso test


4.11.1. Using Android Studio


-

Test 하고자 하는 Java 파일을 우클릭하고 “Run <BlahBlahTest’” 를 클릭한다.



4.11.2. Using Gradle


-

gradle 에서 :app 아래 [verification] 아래 [connectedCheck] task 를 수행한다.




4.12. Checking for a toast


-

@Test
public void ensureListViewIsPresent() throws Exception {
    onData(hasToString(containsString("Frodo"))).perform(click());
    onView(withText(startsWith("Clicked:"))).
    inRoot(withDecorView(
        not(is(rule.getActivity().
        getWindow().getDecorView())))).
        check(matches(isDisplayed()));
}





5. Mocking intents with Espresso Intents


-

Intent 검증은 아래와 같이 한다.

@RunWith(AndroidJUnit4.class)
public class TestIntent {

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

    @Test
    public void triggerIntentTest() {
        onView(withId(R.id.button)).perform(click());
        intended(allOf(
                hasAction(Intent.ACTION_CALL),
                hasData(INTENT_DATA_PHONE_NUMBER),
                toPackage(PACKAGE_ANDROID_DIALER)));
    }
}


-

framework 의 도움 없이 async job 을 test 하는 것은 어렵다.

Espresso 가 있기 전에는 일정 시간을 그냥 기다리거나 source code 에 CountDownLatch 를 사용하는 방법을 사용했다.

Espresso 는 AsynchronousTask 에 관련된 thread pool 을 자동으로 monitor 를 한다.

심지어 event queue 도 monitor 할 수 있다.


만약 IntentService 같은 녀석을 사용한다면 IdlingResource 를 구현해서 써야 한다.

구현은 resource 를 monitor 하고, Espresso framework 에 이를 등록해줘야 한다.



-

public class IntentServiceIdlingResource implements IdlingResource {

    ResourceCallback resourceCallback;
    private Context context;

    public IntentServiceIdlingResource(Context context) {
        this.context = context;
    }

    @Override
    public String getName() {
        return IntentServiceIdlingResource.class.getName();
    }

    @Override
    public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
        this.resourceCallback = resourceCallback;
    }

    @Override
    public boolean isIdleNow() {
        boolean idle = !isIntentServiceRunning();
        if (idle && resourceCallback != null) {
            resourceCallback.onTransitionToIdle();
        }
        return idle;
    }

    private boolean isIntentServiceRunning() {
        ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        // Get all running services
        List<ActivityManager.RunningServiceInfo> runningServices = manager.getRunningServices(Integer.MAX_VALUE);
        // check if our is running
        for (ActivityManager.RunningServiceInfo info : runningServices) {
            if (MyIntentService.class.getName().equals(info.service.getClassName())) {
                return true;
            }
        }
        return false;
    }
}


@RunWith(AndroidJUnit4.class)
public class IntegrationTest {

    @Rule
    public ActivityTestRule rule = new ActivityTestRule(MainActivity.class);
    IntentServiceIdlingResource idlingResource;

    @Before
    public void before() {
        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
        Context ctx = instrumentation.getTargetContext();
        idlingResource = new IntentServiceIdlingResource(ctx);
        Espresso.registerIdlingResources(idlingResource);

    }
    @After
    public void after() {
        Espresso.unregisterIdlingResources(idlingResource);
    }

    @Test
    public void runSequence() {
        // this triggers our intent service, as we registered
        // Espresso for it, Espresso wait for it to finish
        onView(withId(R.id.action_settings)).perform(click());
        onView(withText("Broadcast")).check(matches(notNullValue()));
    }
}






6. Exercise: Creating a custom Espresso matcher


-

BoundedMatcher 라는 것이 있는데 이는 특정 type 의 view 를 match 해주는 Custom Matcher 를 만들게 해준다.

public static Matcher<View> withItemHint(String itemHintText) {
  checkArgument(!(itemHintText.equals(null)));
  return withItemHint(is(itemHintText));
}

public static Matcher<View> withItemHint(final Matcher<String> matcherText) {
  // use preconditions to fail fast when a test is creating an invalid matcher.
  checkNotNull(matcherText);
  return new BoundedMatcher<View, EditText>(EditText.class) {
    @Override
    public void describeTo(Description description) {
      description.appendText("with item hint: " + matcherText);
    }

    @Override
    protected boolean matchesSafely(EditText editTextField) {
      return matcherText.matches(editTextField.getHint().toString());
    }
  };
}





7. Exercise: Write a test for an intent with Espresso


7.1. Create project which is tested



7.2. Write tests



7.3. Validating


-

@Test
public void triggerIntentTest() {
    // check that the button is there
    onView(withId(R.id.button)).check(matches(notNullValue() ));
    onView(withId(R.id.button)).check(matches(withText("Start new activity")));
    onView(withId(R.id.button)).perform(click());
    intended(toPackage("testing.android.vogella.com.simpleactivity"));
    intended(hasExtra("URL", "http://www.vogella.com"));
}





8. Exercise: functional test for activities


8.1. Write functional test for activities





9. Exercise: Testing asynchronous code with Espresso




반응형

댓글0