Espresso API는 테스트 작성자가 사용자의 입장에서 어떻게 애플리케이션과 상호작용 할 것 인가에 대해 생각하도록 해줍니다.
테스트가 디바이스의 UI Thread에서 작동되기 때문에 Espresso는 액티비티나 뷰에 대하여 직접적인 접근하는 것을 방지합니다.
따라서, Espresso에는 getView()
나 getCurrentActivity()
같은 메소드가 없습니다.
테스트 작성자는 그저 ViewAction
과 ViewAssertion
를 상속하는 커스텀 클래스를 만들어서 안전하게 테스트하도록 해야 합니다.
API Components Overview
Espresso의 메인 컴포넌트는 다음과 같습니다.
- Espresso
- onView() 와 onData() 를 통해 뷰와 상호작용 할 수 있는 엔트리 포인트입니다.
- 뷰에 대한 접근이 필수는 아니며, pressBack() 같은 메소드도 있습니다.
- ViewMatcher
- Matcher<? super View> 인터페이스를 구현한 클래스들의 집합입니다.
- onView()의 파라미터로 사용되며, 예시로 withId() 등이 있습니다.
- ViewActions
- ViewAction 인터페이스를 구현한 클래스들의 집합입니다.
- ViewInteraction.perform() 메소드의 파라미터로 쓰이며, 예시로 click() 등이 있습니다.
- ViewAssertions
- ViewAssertion 인터페이스를 구현한 클래스들의 집합입니다.
- ViewInteraction.check() 메소드의 파라미터로 쓰이며, 예시로 matches() 등이 있습니다.
예시
onView(withId(R.id.my_view)) // withId(R.id.my_view) is a ViewMatcher
.perform(click()) // click() is a ViewAction
.check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
ViewMatchers - Find a View
대다수의 경우에 onView() 메소드는 하나의 View에 매칭되는 hamcrest matcher를 취합니다.
(여기서 hamcrest 란, 'match' 규칙이 정의된 matcher 라이브러리로, 주로 테스트 프레임워크에서 사용됩니다.)
Matcher는 Mockito 나 JUnit 을 사용 해보신 분이라면 쉽게 이해하실 수 있을 겁니다.
대표적인 메소드로는 withId()
, withText()
등이 있으며 이외에 메소드를 더 확인하고 싶다면 Developer 사이트를 참고하면 되겠습니다.
어떤 View를 찾기 위해서는, onView()
메소드와 함께 ViewMatcher를 사용해야 합니다. 만약 AdapterView
를 사용하고 있다면 onView()
메소드 대신 onData()
메소드를 사용하면 됩니다.
onView()
메소드는 ViewInteraction 객체를 리턴합니다. (onData()
메소드는 DataIneraction 객체)
다음은 Espresso의 대표적인 matcher 들을 살펴보겠습니다.
withId(int id)
: 파라미터로 받은 id와 resource ID가 일치하는 View 를 탐색합니다.withText(String text)
: 파라미터로 받은 문자열과 일치하는 Text를 가진 View 를 탐색합니다.allOf(Matcher matcher)
: 여러 matcher를 혼합할 수 있습니다. 만약 특정 View 를 제외하고 싶다면 not()을 추가로 사용할 수도 있습니다.
예 - onView(allOf(withId(R.id.button_login), not(withText("Logout "))));
R.id
의 경우 유니크 하지 않을 수 있습니다.**
이 경우 AmbiguousViewMatcherException을 발생 시킵니다.
따라서 non-unique R.id 를 통해 View를 찾는 경우에는 특정 Activity나 Fragment를 통해 찾고자 하는 View가 있는 컨테이너를 찾아 거기로부터 find 할 수 있게끔 해줘야 테스트의 안정성이 높아집니다.
(또는, allOf()
를 이용합니다.)
ViewActions - Perform an action on a view
ViewMatcher를 통해 타겟 View 를 찾았다면, onView()(또는 onData()) 가 리턴한 ViewInteraction(또는 DataInteraction) 의 perform() 메소드와 ViewAction 객체를 통해 타겟 View 에 특정 행동을 취할 수 있습니다.
대표적인 action 들을 살펴보겠습니다. 메소드 명이 명확하기 때문에 자세한 설명은 생략합니다.
click()
typeText()
pressKey()
clearText()
scrollTo()
예시 )
onView(...).perform(click());
onView(...).perform(typeText("Hello"), click());
이외에도 더 많은 메소드를 확인하고 싶다면 역시나 Developer 사이트를 참고하면 되겠습니다.
perfrom() 메소드 또한 onView() 와 마찬가지로 ViewIneraction 객체를 리턴합니다.
이는 메소드 체이닝 방식으로 더 많은 액션을 취할 수 있음을 의미합니다.
ViewAssertions - Verifying test results
앞서 onView() 또는 perform() 메소드 등에서 리턴된 ViewIneraction 객체에는 check() 메소드가 있습니다. 이 ViewIneraction.check() 메소드는 ViewAssertions 객체를 사용하여 테스트 결과를 확인합니다.
ViewAssertions 객체에서 대표적으로 제공하는 메소드는 다음과 같습니다.
matches()
- 파라미터로 ViewMatchers 객체가 사용됩니다.doesNotExist()
참고로, Assertion 의 역할을 onView() 에서 해결하는 것은 좋은 예시가 아닙니다.
예를 들어 특정 View가 "Hello!" 라는 문자열을 갖는지 테스트 할 때에 좋은 예와 나쁜 예를 비교 해보겠습니다.
좋은 예시 )
onView(...).check(matches(withText("Hello!")));
나쁜 예시 )
onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));
ActivityTestRule
Android UI Test 에서는 유닛 테스트의 @Before
처럼 각 테스트마다 전처리 해주어야 할 작업들을 @Rule
어노테이션을 통해 설정할 수 있습니다. @Rule
의 대표적인 사용처인 ActivityTestRule 클래스는 단일 액티비티에서 테스트 할 때에 사용됩니다.
생성자를 통해 액티비티가 테스트가 수행될 때 launch 된 상태로 시작할 것인지, 테스트 이후 별개로 액티비티를 실행하는 코드를 작성할 것인지 선택할 수 있습니다.
ActivityTestRule 객체를 통해서 액티비티를 실행하거나 종료할 수 있습니다.
launchActivity(Intent)
: 파라미터로 넘어온 Intent 를 통해 액티비티를 실행finishActivity()
: 액티비티 종료getActivity()
: 현재 액티비티의 인스턴스
@Rule
어노테이션은 @Before
보다 먼저 수행됩니다.
그리고 테스트가 @After
어노테이션이 달린 메소드까지 수행을 끝마친 후에야 제거됩니다.
샘플
이번에는 앞서 배운 4가지를 모두 활용하여 테스트 해보는 샘플 테스트를 작성 해보겠습니다.
SimpleActivity 에 Button 과 TextView 가 각각 1개씩 있고, 버튼을 클릭하면 TextView 의 텍스트가 "Hello Espresso!" 로 바뀌도록 구현 했다고 가정하겠습니다.
이제 이에 대한 테스트를 작성 해보겠습니다.
-
Test Rule 작성
SimpleActivity 라는 이름의 액티비티 클래스가 있다고 가정하고, Rule을 작성합니다.@Rule public ActivityTestRule<SimpleActivity> rule = new ActivityTestRule<>(SimpleActivity.class);
-
버튼 클릭
SimpleActivity 에서 buton_simple 이라는 id 값을 갖는 버튼을 찾아 클릭하는 테스트입니다.onView(withId(R.id.button_simple)).perform(click());
-
텍스트뷰 검증
그다음 텍스트뷰의 텍스트가 "Hello Espresso!" 로 변했는지 검증합니다.onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));
종합
@RunWith(AndroidJUnit4.class)
@MediumTest
public class ExampleEspressoTest {
@Rule
public ActivityTestRule<SimpleActivity> activityRule =
new ActivityTestRule<>(SimpleActivity.class);
@Test
public void testHelloEspresso() {
onView(withId(R.id.button_simple)).perform(click());
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));
}
}
Filters Annotation
위 예제를 보시면 @MediumTest 라는 어노테이션이 있습니다. 이는 Filter 어노테이션이라고 하는데요.
각각의 종류와 설명은 다음과 같습니다.
-
@FlakyTest
-
concurrency, caching 등 같은 수행에 대해 매번 같은 성공/실패 결과를 내지 않을 수 있는 테스트를 할 때 사용됩니다. 종종 버그를 찾는데 사용됩니다.
-
-
@LargeTest
-
class or method 수준에 적용되는 어노테이션입니다.
-
큰 단위의 테스트에 사용되는 어노테이션입니다. (수행 시간 > 1000 ms)
-
주로 데이터베이스나 네트워크 등 고비용 자원을 사용해야 할 때 사용됩니다.
-
-
@MediumTest
-
@LargeTest 보다는 작은 단위의 테스트에 사용되는 어노테이션입니다. (수행 시간 < 1000 ms)
-
단일 컴포넌트나 작은 규모의 컴포넌트들을 테스트할 때 주로 사용됩니다.
-
데이터베이스나 ContentProvider 등은 접근이 가능하지만 네트워크 사용은 제한됩니다. long-runnging 작업의 경우 mocking 하여 테스트 해야합니다.
-
-
@SmallTest
-
@MediumTest 보다도 작은 단위의 테스트에 사용되는 어노테이션입니다. (수행 시간 < 200 ms)
-
사실상 유닛 테스트에 가깝습니다. @SmallTest 는 특정 논리적인 조건문에 집중하여 검증해 나가야 합니다.
-
파일 시스템, 데이터베이스, 네트워크 등 리소스에 대한 권한이 주어지지 않기 때문에 외부 리소스에 대해서는 mocking 하여 테스트해야 합니다.
-
-
@RequiresDevice
-
실제 물리적인 디바이스에서만 테스트가 동작합니다. 에뮬레이터에서는 동작하지 않습니다.
-
-
@SdkSuppress
-
테스트가 수행될 minimum or maximum API Level 을 설정할 수 있습니다.
-
class or method 단위에 적용할 수 있습니다.
-
-
@Suppress
-
테스트에서 제외시키는 어노테이션입니다.
-
이 어노테이션이 붙은 테스트는 테스트 되지 않습니다.
-
'Android > Test' 카테고리의 다른 글
[Android UI Test] 6) Espresso 를 활용한 WebView Test (0) | 2020.03.10 |
---|---|
[Android UI Test] 5) Espresso 를 활용한 RecyclerView Test (0) | 2020.03.09 |
[Android UI Test] 4) Espresso Intended 와 Intending (0) | 2020.03.08 |
[Android UI Test] 2) Espresso 설치 및 환경 구축 (0) | 2020.03.06 |
[Android UI Test] 1) Espresso 란? (0) | 2020.03.06 |