단일 앱 UI 테스트하기
단일 앱 내에서 사용자 상호작용을 테스트 하는 것은 사용자가 예상하지 못한 결과를 마주하거나, 앱과 상호작용 할 때 좋지 않은 경험을 하지 않을 것을 확인해 줍니다. 앱의 UI 가 오바르게 동작하는 것을 확인하는 것이 필요하다면, UI테스트를 만드는 습관을 들이는 것이 좋습니다.
Espresso 테스트 프레임 워크는 안드로이드 테스트 서포트 라이브러리에서 제공되며, 사용자 상호작용을 단일 타겟 앱에서 테스트하는 API를 제공합니다. Espresso 테스트는 안드로이드 버전 2.33(API 레벨 10) 이상의 기기에서 실행가능합니다. Espresso의 가장 주요한 이점은 테스트 하는 앱의 UI 테스트 액션의 자동 동기화라고 할 수 있습니다. Espresso는 메인 스레드가 쉬고(idle) 있는 상태를 감지합니다.그렇기에 적절한 시기에 테스트 커맨드를 수행하고 테스트의 신용도를 올릴 수 있습니다. 이 능력은 Thread.sleep같은 스케쥴링 작업을 추가하는 것으로부터 자유롭게 해줍니다.
Espresso 테스트 프레임워크는 지시 기반(instrumentation-based API)에 해당하며, AndroidJUnitRunner 테스트 러너와 함께 동작합니다.
Espresso 셋업하기
UI 테스트를 Espresso로 만들기 전에 테스트 코드의 위치와 프로젝트의 의존성을 확인하십시오. 자세한 사항은 테스트 시작하기를 참조해 주십시오.
depndencies{
//Other dependecies
androidTestCompile 'com.android.support.test.espresso.:espresso-core:2.2.2'
}
테스트 기기의 에니메이션을 끄십시오. - 테스트 기기에서 에니메이션을 켜놓는 것은 예상하지 못한 결과를 만들어 낼 수 잇습니다. 또는 테스트가 실패하게 할 수도 있습니다.애니메이션을 끄는 것은 개발자 설정에서 아래의 사항들을 끄는 것으로 할 수 있습니다.
- Window animation scale
- Trasition animation scale
- Animator duration scale
코어 APi가 제공하는 것 외에 다른 Espresso의 기능을 프로젝트에서 쓰고 싶다면 여길 참조해 주십시오. resource
Espresso 테스트 클래스 만들기
Espresso 테스트를 만들기 위해서 자바 클래스를 아래의 프로그래밍 모델에 따라 만드십시오.
- Activity내 테스트 하고 싶은 UI요소를 찾으십시오(예를 들면 로그인 버튼이 있습니다).onView()메소드를 사용하거나 AdapterView에서 onData()메소드를 사용할 수 있습니다.
UI 요소에서 이루어질 사용자 상호작용을 시뮬레이트 하십시오. ViewInteraction.perform()또는 DataInteraction.perform()메소드를 사용해 사용자 동작을 전달합니다(로그인 버튼 클릭) 일렬의 여러 동작을 동일한 UI요소에 입력하기 위해서 그들을 콤마-분리 방식으로 메소드 요소에 나열 할 수 있습니다.
필요한만큼 위의 과정을 반복해 여러 액티비티에 걸쳐 사용자 흐름을 시뮬레이트 하십시오.
ViewAssertions 메소드들을 이용해 이러한 사용자 상호작용이 이뤄진 후에 UI가 예상되는 상태나 동작을 하는지 확인하십시오.
이러한 과정은 아래에서 더 자세하게 다뤄집니다.
이어지는 예제 코드 호출될 기본적인 작업흐름을 보여줍니다.
onView(withId(R.id.myView)) //WithId(R.id.my_view) 는 ViewMatcher입니다.
.perform(click())//click()은 ViewAction입니다.
.check(matches(isDisplayed)));//matches(isDisplayed())는 ViewAssertion입니다.
ActivityTestRule와 함께 Espresso 사용하기
이번 과정은 JUnit 4 스타일로 Espresso 테스트를 만들고 ActivityTestRule를 사용해 반복적으로 작성해야하는 코드를 줄이는 방법을 배워보겠습니다. ActivityTestRule을 사용해, 테스트 프레임워크가각각의 @Test 어노테이션이 달린 테스트 메소드와 @before 어노테이션이 달린 before메소드를 진행하기 전에 액티비티를 실행하도록하고 합니다.
이 프레임워크는 테스트가 종료되고 @after 어노테이션이 달린 메소드들이 모두 종료된 후에 액티비티를 닫습니다.
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 mStringToBeytyepd;
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
@Before
public void initValidString(){
mStringToBeTyped = "Espress";
}
@Test
public void changeTest_sameActivity(){
onView(withId(R.id.editTextUserInput))
.perform(type/text(mStringToBetyped}, closeSoftKeyboard());
onView(withId(R.id.changeTextBt)).perform(click());
onView(withId(R.id.textToBeChanged)).check(matches(withText(mStringToBeTyped)));
}
}
UI 요소에 접근하기
테스트에서 Espresso가 앱과 상호작용하기 이전에, UI 구성요소 또는 View를 특정해야합니다.Espresso는 Hamcrest matchers를 사용해서 앱에서 뷰 또는 어뎁터를 특정합니다.
뷰를 찾기 위해서, onView()메소드를 호출하고 view matcher를 전달해 타겟팅한 뷰를 특정합니다. 자세한 내용은 View Matcher 특정하기에서 다뤄집니다. onView()메소드는 View와 상호작용하는 테스트를 할 수있도록 해주는 ViewInteraction 객체를 반환합니다. 그러나,RecyclerView 레이아웃에 존재하는 View의 경우 onView메소드를 호출하는 것이 정상동작하지 않을 것입니다. 이 경우에는 AdapterView에 있는 View의 지시사항을 따라주십시오.
주의: onView()메소드는 찾으려는 뷰가 유효한지 확인하지 않습니다. 대신 Espresso는 현재의 View 계층 구조에서만 제공된 matcher에 따라 View를 검색합니다. 어떠한 일치 사항도 발견하지 못하면, NoMatcingViewException을 만듭니다.
아래의 예제는 EditText필드에 접근해, 문자열을 입력하고 가상 키보드를 닫은 후에 버튼을 클릭하는 과정을 담고 있습니다.
public void testChangeText_sameActivit(){
onView(withId(R.id.editTextUserInput()}
.perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard());
onView(withId(R.id.changeTextButton)).perform(click());
...
View Matcher 특정하기
아래의 방법으로 View Matcher를 특정할 수 있습니다.
- ViewMatchers 클래스에서 메소드 호출하기 예를들면, 화면에 표시되는 문자열로 View를 찾는다면 아래의 메소드를 호출할 수 있습니다.
onView(withText("Sign-in");
유사하게 withId()를 호출하고 리소스 ID (R.id)를 넣을 수 있습니다.
onView(withId(R.id.button_signin)):
안드로이드 리소스 아이디는 동일한 아이디가 한 개 이상 존재할 수 있습니다. 만약 리소스 ID에 일치하는 결과가 한 개의 View보다 많으면 Espresso 는 AmbiguousViewMatcherException을 만듭니다.
- Hamcrest Mathers클래스를 이용합니다. allOf()메소드를 사용해 여러개의 matcher를 합칠 수 있습니다. 예를 들어 containsString()과 instanceOf()를 사용해 보겠습니다. 이 방법은 필터의 결과를 더 좁게 만듭니다.
onView(allOf(withId(R.id.button_signin), withText("sign-in")));
not 키워드를 사용해 matcher에 일치하지 않는 view를 걸러낼 수도 있습니다.
onView(allOf(withId(R.id.button_signin),not(withText("Sign_out"))));
이러한 방법들을 사용하기 위해서는 org.hamcrest.matchers패키지를 추가해야합니다. 더 자세하게 배우고 싶다면, Hamcrest site를 참조해 주십시오.
Espresso 성능 향상을 위해서 가능한 최소한의 정보로 View를 찾으시길 권합니다. 예를 들어 특정 문자열로 뷰의 구분이 가능하다면, 굳이 TextView의 인스턴스다 라는 조건을 걸 필요가 없습니다.
AdpaterView에 있는 View
AdapterView의 위젯에서 View는 동적으로 하위 뷰와 함께 만들어집니다. 만약에 목표한 뷰가 AdapterView안에 있다면(예를 들어 ListView, GridView, Spinner),onView()동작이 아마 동작하지 않을 수도 있습니다. 왜냐면 현재의 뷰 계층구조에서 전체의 일부분만 로딩되었을 수도 있기 때문입니다.
대신 onData()메소드를 호출해 DataInteraction객체를 얻어 목표 뷰 요소에 접근하십시오. Espresso는 현재 뷰 계층구조에 해당 목표 뷰를 불러오도록 조종합니다. Espresso는 또한 해당 뷰로의 스크롤을 해 해당 뷰가 포커스에 드로오도록 합니다.
주의: onData()메소드는 일치하는 뷰가 있는지 확인하지 않습니다. Espresso는 오로지 현재 뷰 계층 구조만을 검색합니다. 만약에 일치하는 것이 없다면, NoMatchingViewException을 만듭니다.
아래의 예제는 onData()를 Hamcrest로 주어진 문자열을 가지고 있는 특정 열을 찾는 방법을 다루고 있습니다. 이 예제에서, LongListActivity 클래스는 simpleAdapter를 통해서 문자열 리스트를 가지고 있습니다.
//맵 클래스에 ROW_TEXT)라는 엔트리를 가지고 있으며 "test input"이라는 문자열을 가지고 있는 데이터
onData(allOf(is(instanceOf(Map.class)),hasEntry(equalTo(LongListActivity.ROW_TEXT),is("test input")));
동작 실행하기
ViewInteraction.perform()또는 DataInteraction.perform()메소드를 호출해서 UI요소에 대한 사용자 상호작용을 시뮬레이트 할 수 있습니다. 인자로 반드시 하나 이상의 ViewAction 객체를 전달해야 합니다. Espresso는 각각의 동작을 그 순서에 따라서 메인 스레드에서 실행합니다.
ViewActions 클래스는 특정 동작들에 대해서 헬퍼를 제공합니다. 이를 사용해 각각의 ViewAction 객체를 구성하는 것 대신에 빠르고 편리하게 메소드를 사용할 수 잇습니다. 아래와 같은 동작들이 있습니다.
ViewActions.click(): 뷰 클릭하기
ViewActions.typeText(): 뷰를 클릭하고 특정 문자열 입력하기
ViewActions.scrollTo: 뷰로 스크롤합니다. 해당 뷰가 반드시 ScrollView의 하위 뷰여야하며 android:visibility값이 VISIBLE이여야 합니다. AdapterView를 상속한 뷰(예로 ListView)의 경우 onData()메소드가 스크롤을 합니다.
ViewActions.pressKey(): 특정 키코드를 누르는 동작을 수행합니다.
- ViewActions.clearText(): 해당 뷰에 문자열을 지웁니다.
ScrollView안의 뷰의 경우 ViewActions.srollTo()액션은 먼저 뷰를 화면에 보이게 한 후 다른 동작을 수행합니다. 만약에 해당 뷰가 화면에 이미 보이고 있다면, ViewActions.scrollTo()는 아무런 영향력이 없습니다.
Espresso 인텐트로 개별 액티비티 테스트하기
Espresso 인텐트는 앱에의해 보내지는 인텐트를 스터빙하거나 입증할 수 이습니다. Espresso 인텐트로 앱, 액티비티 또는 서비스를 방출되는 인텐트를 가로 채거나 결과를 스터빙하거나 테스트 중인 컴포넌트로 돌려보내 개별적으로 테스트 할 수 있습니다.
Espresso 인텐트로 테스트하기 위해 , 아래의 코드를 build.gradle에 추가해야 합니다.``
dependencies{
androidTestCompile 'com.android.support.test.espresso:espresso0intent:2.2.2'
}
인텐트를 테스트하기 위해서는 IntentTestRule 클래스의 인스턴스를 만들어야만 합니다. 이 인스턴스는 AcitivityTestRule과 매우 유사합니다. 이 IntentTestRule 클래스는 EspressoIntent를 각각의 테스트 이전에 초기화 하고, 호스트 액티비티를 종료하고 각각테스트에 맞게 Espresso Intent를 방출합니다.
아래의 예제에서 보이는 테스트 클래스는 명시적 Intent의 간단한 예제입니다. 테스트의 액티비티와 인텐트들은 첫 앱 만들기 튜토리얼에 있는 것들입니다.
@Large
@RunWIt(AndroidJUnit4.class)
public class SimpleIntentTest{
private static final String MESSAGE = "This is a test";
private sattic final String PACKAGE_NAME = "com.example.myfirstapp";
@Rule
public IntentsTestRule<MainActivity> mIntentRule = new IntentsTestRule<>(MainActivity.class);
@Test
public void verifyMessageSentToMessageActivity(){
// EditText에 메세지를 입력합니다.
onView(withId(R.id.edit_message))
.perform(typeText(MESSAGE), closeSoftKeyDoard());
//버튼을 클릭해 다른 액티비티로 명시적 인텐트를 통해 메세지를 보냅니다.
onView(withId(R.id.send_message)).perform(click());
//DisplayMessageActivity가 올바른 패키지 이름과 메세지를 인텐트로 받았는지 확인합니다.
intended(allOf(hasComponent(hasShortClassName(".DisplayMessageActivity")),
toPacakage(PACKAGE_NAME),
hasExtra(MainActivity.EXTRAMESSAGE,MESSAGE)));
}
}
Espresso 인텐트에 대한 더 많은 정보는 안드로이드 테스트 서포트 라이브러리의 Espresso 인텐트 문서 에서 확인하실 수 있습니다. 예제 IntentBasicSample과 IntentAdvancedSample코드를 다운받을 수도 있습니다.
Espresso Web으로 WebViews테스트하기
Espresso Web은 액티비티 내 WebView 구성요소를 테스트 할 수 있도록 해줍니다. 이는 WebDriver API를 사용해 WebView의 동작을 확인하고 조작합니다.
Espresso Web테스트를 시작하기 위해서 아래의 코드를 build.gralde에 추가해 주십시오.
...Todo 번역 필요
결과 확인하기
ViewInteraction.check()나 DataInteraction.check()를 호출해서 UI가 예상되는 상태를 가지고 있는지 확인할 수 있습니다. 이 메소드들에겐 ViewAssertion 객체를 인자로 전달해야합니다. 만약 평가가 실패한다면, Espresso는 AssertionFailedError를 만들어 냅니다.
ViewAssertions 클래스는 특정 어설트에 대한 헬퍼를 제공합니다. 해당 어설트는 아래를 포함합니다.
doesNotExist: 뷰 계층 구조에 일치하는 뷰가 없을 경우를 의미합니다..
matches: 특정뷰가 현재의 뷰 계층구조에 존재하는지 확인하며 특정 hamcrest matcher에 맞는 상태인지 확인합니다.
- selectedDescendentsMatch: 특정 하위 뷰가 어떤 부모 뷰에 존재하는지 확인합니다. 그리고 hamcrest mather에 맞는 상태를 가지고 있는지 확인합니다.
아래의 예제는 UI상에 EditText에 입력한 문자열이 표시되는지 확인하는 코드입니다.
public void testChangeText_sameActivity(){
//텍스트를 입력하고 버튼을 누릅니다
// 텍스트가 변했는지 확인합니다.
onView(withId(R.id.textToBeChanged)).check(matches(withText(STRING_TO_BE_TYPED)));
}
기기나 에뮬레이터에서 Espresso 테스트 실행하기
...TODO 번역 ㄹ요