[android] Espresso Tutorial |
http://www.vogella.com/tutorials/AndroidTestingEspresso/article.html
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
'프로그래밍 놀이터 > 안드로이드, Java' 카테고리의 다른 글
[Gradle] compile(api) vs. implementation (1) | 2019.02.06 |
---|---|
[android] Android unit and instrumentation tests tutorial (0) | 2019.02.05 |
[android] gradle build 성능 향상시키기 (0) | 2019.02.03 |
[android] Browser 로부터 Share intent 받아 처리하기 (0) | 2019.02.02 |
[android] browser 에서 link 클릭했을 때 app selection 없이 내 앱으로 연결하기 (app link) (2) | 2019.02.01 |
댓글