[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 |
댓글