Espresso-Intents 패키지는 테스트할 때 Intent에 대해 stubbing을 할 수 있게 지원 해줍니다.
쉽게 말해 Android Intent를 위한 Mockito 역할을 한다고 보면 되겠습니다.
테스트할 앱이 다른 앱이나 플랫폼에 기능을 위임하는 경우 Espresso-Intents
를 사용하여 다른 앱이나 플랫폼이 올바르게 작동한다고 가정할 수 있습니다. 또, 테스트 앱으로부터 나가는 내용을 stubbing 하거나 검증할 수 있으며 실제 Intent 응답 값 대신 stub intent response 를 받을 수 있습니다.
Setup
앱 수준의 build.gradle
파일에 다음과 같이 디펜던시를 추가해줍니다.
dependencies {
//...
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test:rules:1.1.0'
}
Write Test Rules
Espresso-Intents 테스트를 하기에 앞서 IntentsTestRule
을 세팅해야 합니다.
이는 ActivityTestRule
의 확장이며, 이는 Espresso-Intents API를 사용하여 UI Test 를 보다 쉽게 해줍니다.
IntentsTestRule
은 @Test
어노테이션이 붙은 각 테스트를 테스트하기에 앞서 매번 초기화 됩니다. Unit Test 에서 @Before
의 기능과 유사하다고 보면 되겠습니다.
@Rule
public IntentsTestRule<MyActivity> intentsTestRule =
new IntentsTestRule<>(MyActivity.class);
Intent Test
인텐트를 테스트할 때에도 역시나 Hamcrest Matchers 를 적극 활용합니다. Hamcrest 를 사용하여 인텐트를 테스트하는 데에는 다음 두 가지로 나눌 수 있습니다.
- 이미 존재하는 인텐트 matcher 사용 (가장 많이 사용 되는 방법)
- 커스텀 인텐트 matcher 구현 - 유연하게 hamcrest matcher 를 사용하여 필요에 따라 matcher 커스텀
두 번째 방법인 커스텀 인텐트 matcher 의 경우 Espresso-Intents 에서 제공하는 Intended() 와 Intending() 을 사용하여 인텐트를 validation 및 stubbing 할 수 있습니다.
첫 번째 방법은 아래 예시와 같이 사용됩니다.
assertThat(intent).hasAction(Intent.ACTION_VIEW);
assertThat(intent).categories().containsExactly(Intent.CATEGORY_BROWSABLE);
assertThat(intent).hasData(Uri.parse("www.google.com"));
assertThat(intent).extras().containsKey("key1");
assertThat(intent).extras().string("key1").isEqualTo("value1");
assertThat(intent).extras().containsKey("key2");
assertThat(intent).extras().string("key2").isEqualTo("value2");
보시다시피 인텐트의 Action, Category, Extra 등 다양한 값들을 검증할 수 있습니다.
참고로, MockitoAnnotations.initMocks 처럼 인텐트를 검증할 때에는 @Before
단계에서 init()
메소드를 통해 초기화 해야합니다. 그리고 각 테스트가 끝날때마다 release()
해줘야 합니다.
Intended
만약 MainActivity 에서 Contacts(연락처) 앱의 Activity를 실행하여 정보를 가져온다면, Contacts 가 올바른 Intent 를 받았는지 테스트하기 힘들 수 있습니다. Component 가 내 앱에 구현되어 있지 않고 다른 앱 내에 있기 때문입니다.
Intended 는 이런 테스트를 하는데 도움을 주는 기능입니다. MainActivity 에서 인텐트를 전달할 때 이것을 가로채서 의도된 인텐트인지 확인을 합니다.
intended()
는 Mockito.verify()
와 비슷하다고 보면 됩니다. 테스트 작성자는 이를 통해 주어진 인텐트를 검증할 수 있습니다.
intended() 메소드는 다음 두 가지 종류가 있습니다.
-
void intended(Matcher<Intent> matcher, VerificationMode verificationMode)
이는 Mockito 의
verify(mock, times(num))
과 유사합니다.
주어진 횟수와 일치하는 수의 intent 가 matcher 에 부합 하는지를 검증합니다.이때 검증은 인텐트가 전송된 것과 동일한 순서로 수행되지 않아도 됩니다.
Intents.init()
이 호출된 시점부터 기록됩니다. -
void intended(Matcher<Intent> matcher)
주어진 matcher와 테스트하는 앱의 인텐트가 오직 한 번만 부합 하는 것을 검증합니다.
이는 Mockito의
verify(mock, times(1))
과 같습니다.마찬가지로 인텐트가 전송된 것과 동일한 순서로 수행되지 않아도 되며
Intents.init()
이 호출된 시점부터 기록됩니다.
FirstActivity 의 버튼을 클릭 했을 때 SecondActivity 로 전환되면서
Intent 에 Extra 값(Key : SimpleActivity.FOO
, Val : "foo"
)을 담아서 전달하는 앱이 있다는 가정 하에 간단한 테스트를 작성해보겠습니다.
@Test
public void simpleIntended() {
onView(withId(R.id.button_sample))
.perform(click())
Context context = InstrumentationRegistry
.getInstrumentation()
.getTargetContext();
String foo = context.getString(R.string.foo);
intended(hasExtra(SecondActivity.FOO, foo));
intended(toPackage("com.ready.uitest"));
intended(IntentMatchers.hasComponent(
"com.ready.uitest.SecondActivity"));
intended(allOf(
hasExtra(SecondActivity.FOO, foo),
toPackage("com.ready.uitest"),
IntentMatchers.hasComponent("com.ready.uitest.SecondActivity")));
}
위 코드처럼 FirstActivity 에서 SecondActivity 로 전환하면서 전달된 인텐트에 대해서 테스트할 수 있었습니다.
하지만, intended
는 보내는 인텐트에 대해서만 테스트 할 수 있을 뿐이지 다른 안드로이드 컴포넌트로부터 응답 받은 인텐트를 확인할 수는 없습니다. 따라서 받은 인텐트에 대해 검증하고 싶을 때에는 intended()
가 아닌 intending()
을 사용해야 합니다.
Intending
intending()
메소드는 Mockito.when()
과 유사합니다. 이를 통해 테스트 작성자는 startActivityForResult()
로 실행된 액티비티의 응답을 stubbing 할 수 있습니다.
테스트 하는 액티비티의 외부 액티비티 UI 에 대해서는 다룰 수 없기 때문에 ActivityResult
로 받아오는 결과에 대해 다룰 수 있게 해주는 intending()
메소드가 유용하게 사용됩니다.
테스트 작성자는 intending(matcher)
가 리턴하는 OngoingStubbing
객체를 통해 추가적으로 메소드를 이어붙여 launching 액티비티가 올바르게 결과를 다루는지도 확인할 수 있습니다.
- intending(Matcher matcher).respondWith(Instrumentation.ActivityResult result)
- intending(Matcher matcher).respondWithFunction(ActivityResultFunction result)
앞서 Intended 에서 살펴본 예제를 수정하여 SecondActivity
에서 버튼을 클릭하면 FirstActivity
로 결과를 반환하도록 구현했다고 가정하겠습니다.
이때 intending()
은 SecondActivity
가 FirstActivity
로부터 인텐트를 받아 어떻게 처리했는지를 중요하게 생각하지 않습니다. 오직 SecondActivity
가 인텐트를 결과로 리턴할 때 FirstActivity
가 의도한대로 처리되는지 테스트 하는데 초점이 맞춰져 있습니다.
SecondActivity
가 내부적으로 인텐트를 어떻게 처리하는지 중요하지 않습니다. 왜냐하면 결과로 리턴 받는 인텐트를 직접 설정할 수 있기 때문입니다. 결과로 인텐트를 전달 받으면 FirstActivity
내에서 이 인텐트를 처리하고 View 에 결과물을 출력해줍니다. 그리고 onView(...)
를 이용하여 의도된 결과를 출력하는지 확인합니다.
그럼 FirstActivity를 기준으로 한 테스트 코드를 작성해보겠습니다.
@Test
public void simpleIntending() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
String foo = context.getString(R.string.locale_korea);
Intent intent = new Intent();
intent.putExtra(FirstActivity.FOO, foo);
Instrumentation.ActivityResult result =
new Instrumentation.ActivityResult(Activity.RESULT_OK, intent);
intending(toPackage("com.ready.uitest"))
.respondWith(result);
onView(withId(R.id.button_sample)).perform(click());
onView(withId(R.id.text_result)).check(matches(withText(foo)));
}
SecondActivity
에서 리턴될 것이라고 예상되는 인텐트를 모두 설정하고, perform(click())
을 호출하면 설정한 인텐트가 리턴됩니다.
실제로 SecondActivity
를 실행하지 않기 때문에 SecondActivity
에서 버튼을 누를 필요도 없습니다. "foo" 가 인텐트로 전달되기 때문에 R.id.text_result
에 "foo" 가 출력될 것이고, check(...)
로 예상 결과와 일치하는지 확인합니다.
이렇게 Intended 와 Intending 을 이용하여 테스트 코드를 작성하는 방법에 대해서 알아보았습니다.
기억해야 할 것은 이 두개의 기능이 안드로이드 컴포넌트의 의존성을 끊고 각각 독립적으로 테스트할 수 있도록 도와준다는 것입니다.
'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] 3) Espresso API 제대로 알고 사용하기 (0) | 2020.03.07 |
[Android UI Test] 2) Espresso 설치 및 환경 구축 (0) | 2020.03.06 |
[Android UI Test] 1) Espresso 란? (0) | 2020.03.06 |