advanced espresso #io16 extend seoul

48
정승욱 Google Developer Expert 토스랩 - JANDI Android 개발자 Advanced Espresso

Upload: seongug-jung

Post on 17-Jan-2017

1.688 views

Category:

Internet


1 download

TRANSCRIPT

Page 1: Advanced espresso   #io16 extend seoul

정승욱

Google Developer Expert토스랩 - JANDI Android 개발자

Advanced Espresso

Page 2: Advanced espresso   #io16 extend seoul

안드로이드 테스트

안드로이드

스튜디오

안드로이드 테스트서포트 라이브러리

AndroidEpsresso

Page 3: Advanced espresso   #io16 extend seoul

UI 테스트 흐름

Page 4: Advanced espresso   #io16 extend seoul

onView(withId(R.id.fab)).perform(click());

onView(withItemText("Jason")).perform(click());

onView(withId(R.id.et_message))

.perform(typeText("test"));

onView(withId(R.id.btn_send)).perform((click());

onView(withId(android.R.id.home).perform(click());

onView(withItemText("Jason"))

.check(matches(isDisplayed()));

코드로 보는 UI 테스트

Page 5: Advanced espresso   #io16 extend seoul

onView(withId(R.id.fab)).perform(click());

onView(withItemText("Jason")).perform(click());

onView(withId(R.id.et_message))

.perform(typeText("test"));

onView(withId(R.id.btn_send)).perform((click());

onView(withId(android.R.id.home).perform(click());

onView(withItemText("Jason"))

.check(matches(isDisplayed()));

코드로 보는 UI 테스트

Page 6: Advanced espresso   #io16 extend seoul

onView(withId(R.id.fab)).perform(click());

onView(withItemText("Jason")).perform(click());

onView(withId(R.id.et_message))

.perform(typeText("test"));

onView(withId(R.id.btn_send)).perform((click());

onView(withId(android.R.id.home).perform(click());

onView(withItemText("Jason"))

.check(matches(isDisplayed()));

코드로 보는 UI 테스트

Page 7: Advanced espresso   #io16 extend seoul

onView(withId(R.id.fab)).perform(click());

onView(withItemText("Jason")).perform(click());

onView(withId(R.id.et_message))

.perform(typeText("test"));

onView(withId(R.id.btn_send)).perform((click());

onView(withId(android.R.id.home).perform(click());

onView(withItemText("Jason"))

.check(matches(isDisplayed()));

코드로 보는 UI 테스트

Page 8: Advanced espresso   #io16 extend seoul

Espresso 의 구분

onView(Matcher<View>) // ViewMatcher -> ViewInteraction .perform(ViewAction) // ViewAction

onView(Matcher<View>) .check(ViewAssertion); // ViewAssertion

Page 9: Advanced espresso   #io16 extend seoul

ViewMatcher

View 에 접근하기 위한 객체

Activity 나 Fragment 를 사용하면?

➡� View 가 Null 이면? ➡� Test에 NPE 처리를?

“ViewMatcher 는 뷰에 접근하는 과정에서의 오동작을 에러가 아닌 테스트 실패로 간주할 수 있도록 도와준다.”

Page 10: Advanced espresso   #io16 extend seoul

ViewInteraction

UI 테스트의 시작점

접근한 View 정보를 담고 있음

View 의 동작을 제어 : 클릭, 텍스트 입력 등

View 의 정보를 검증 기능 제공 : 화면에 보이는지..

Page 11: Advanced espresso   #io16 extend seoul

ViewAction

뷰에 클릭 또는 텍스트 입력등 다양한 동작을 제어함

동작이 완료될 때까지 대기하도록 함

Page 12: Advanced espresso   #io16 extend seoul

ViewInteraction.java

Page 13: Advanced espresso   #io16 extend seoul

Idle or not?

handler.postDelayed(runnable, 5000);

Main Looper 는 Idle 상태일까요?

Page 14: Advanced espresso   #io16 extend seoul

LooperIdlingResource.java

Page 15: Advanced espresso   #io16 extend seoul

QueueInterrogator.java

Page 16: Advanced espresso   #io16 extend seoul

Custom IdlingResource 예시

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

private boolean isIntentServiceRunning() { ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningServiceInfo info : manager.getRunningServices(Integer.MAX_VALUE)) { if (RepeatService.class.getName().equals(info.service.getClassName())) { return true; } } return false;}

Page 17: Advanced espresso   #io16 extend seoul

Custom IdlingResource 적용

@Beforepublic void registerIntentServiceIdlingResource() { Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); idlingResource = new IntentServiceIdlingResource( instrumentation.getTargetContext()); Espresso.registerIdlingResources(idlingResource);}

@Afterpublic void unregisterIntentServiceIdlingResource() { Espresso.unregisterIdlingResources(idlingResource);}

Page 18: Advanced espresso   #io16 extend seoul

구글의 팁

Page 19: Advanced espresso   #io16 extend seoul

복사 붙여넣기 하지마라

Page 20: Advanced espresso   #io16 extend seoul

복붙 금지!!!

@Test public void testXXX() { onView(withId(R.id.fab)).xxx;}

@Test public void testYYY() { onView(withId(R.id.fab)).yyy;}

만약 Resource 의 ID 가 바뀐다면?

Page 21: Advanced espresso   #io16 extend seoul

Robot 예제

42 입력

onView(withText("4")).perform(click());onView(withText("2")).perform(click());

Robot.input(42);

public static void input(int x) { String y = String.valueOf(x); for (int i = 0; i < y.lengn(); i++) { onView(withText(String.valueOf(y.charAt(i)))) .perform(click()); }}

Page 22: Advanced espresso   #io16 extend seoul

가능한 제공되는 Matcher 를 사용해라

Page 23: Advanced espresso   #io16 extend seoul

CheatSheet

Page 24: Advanced espresso   #io16 extend seoul

CountingIdlingResource 를 사용해라

Page 25: Advanced espresso   #io16 extend seoul

CountingIdlingResource.java

public class CountingIdlingResource {

public void increment();

public void decrement();

}

Page 26: Advanced espresso   #io16 extend seoul

단 Timeout 설정을 같이 해주세요.

public class IdlingPolicies {

public static void setMasterPolicyTimeout(long timeout,TimeUnit unit);

public static void setIdlingResourceTimeout(long timeout, TimeUnit unit);

}

기본값- IdlingResource : 5초- MasterPolicy : 26초

Page 27: Advanced espresso   #io16 extend seoul

뷰의 정보가 아닌 동작에 집중해라.

Page 28: Advanced espresso   #io16 extend seoul

동작의 결과에 주목하자.

4가 쓰여진 뷰의 x-y 위치 같은 것은 잊어라

4가 씌여진 뷰가 있는지를 검증하라.

Page 29: Advanced espresso   #io16 extend seoul

Large Test 보단 Small Test 를 많이 써라

Page 30: Advanced espresso   #io16 extend seoul

LargeTest

Page 31: Advanced espresso   #io16 extend seoul

Small Test

1 2

2 3

3 4

Page 32: Advanced espresso   #io16 extend seoul

원하는 화면을 바로 호출해라.

Page 33: Advanced espresso   #io16 extend seoul

MyActivityTest.java

@Rule

new ActivityTestRule<MyActivity>(MyActivity.class) {

@Override protected Intent getActivityIntent() {

Intent intent = new Intent();

intent.putExtra(...);

return intent;

}

}

Page 34: Advanced espresso   #io16 extend seoul

MyActivityTest.java

@Rulepublic ActivityTestRule<MyActivity> rule = new ActivityTestRule<MyActivity>(MyActivity.class, true, false);

@Beforepublic void setUp() { int extra = getExtraInt(); Intent intent = new Intent(); rule.launchActivity(intent);}

Page 35: Advanced espresso   #io16 extend seoul

통제된 환경에서만 테스트 해라

Page 36: Advanced espresso   #io16 extend seoul

외부 앱 실행은 Intent 를 획득하라

Page 37: Advanced espresso   #io16 extend seoul

Intent 획득

@Testpublic void test() { Intents.init(); ActivityResult result = createImageCaptureResult(); intending(hasAction(IMAGE_CAPTURE)).responseWith(result); // test something Intents.release();}

Page 38: Advanced espresso   #io16 extend seoul

Intent 획득

@Rulepublic IntentTestRule<MyAct> rule = new IntentTestRule<>(MyAct.class);

@Testpublic void test() { ActivityResult result = createImageCaptureResult(); intending(hasAction(IMAGE_CAPTURE)).responseWith(result); // test something}

Page 39: Advanced espresso   #io16 extend seoul

애니메이션을 핸들링 하기

Page 40: Advanced espresso   #io16 extend seoul

이따금 커스텀 애니메이션은 과도하게 Handler 를 사용하기 때문에 Idle 상태를 유지하기 어렵게 만든다.

Page 41: Advanced espresso   #io16 extend seoul

UI 테스트에 실패했을 때...

Page 42: Advanced espresso   #io16 extend seoul

Espresso ViewHierarchy Log

Test 의 Log Console 을 읽는다.

android.support.test.espresso.AmbiguousViewMatcherException: 'with id: is <2131493330>' matches multiple views in the hierarchy. Problem views are marked with '****MATCHES****' below.

+------------->ImageView{id=2131493330, res-name=item_image, desc=Image, visibility=VISIBLE, width=262, height=262, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0} ****MATCHES****

+------------->ImageView{id=2131493330, res-name=item_image, desc=Image, visibility=VISIBLE, width=262, height=262, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0} ****MATCHES**** |

Page 43: Advanced espresso   #io16 extend seoul

Custom FailureHandler

public interface FailureHandler {

public void handle(Throwable error, Matcher<View> viewMatcher);

}

Espresso.setFailureHandler(handler);

Page 44: Advanced espresso   #io16 extend seoul

느린 기기 Test 시 주의사항

Page 45: Advanced espresso   #io16 extend seoul

Settings → Accessiblility → Touch and hold delay

Long 으로 전환

Animation 비활성화

Page 46: Advanced espresso   #io16 extend seoul

Accessibility Test 시 주의사항

Page 47: Advanced espresso   #io16 extend seoul

AccessibilityValidator.enable()

Page 48: Advanced espresso   #io16 extend seoul

참고 자료

문서

- https://google.github.io/android-testing-support-library/

영상

- https://www.youtube.com/watch?v=isihPOY2vS4

예제 코드- https://github.com/googlesamples/android-testing- https://github.com/googlesamples/android-testing-templates- https://github.com/googlesamples/android-architecture