이번에는 UI 테스트에서 사용하는 ActivityRule
에 대해서 설명할 것이다.
Rule 에는 ActivityTestRule
, ActivityScenarioRule
두 가지가 존재한다.
또, Rule 없이 사용하는 방법으로 ActivityScenario
가 존재한다.
구글은
ActivityScenarioRule
나ActivityScenario
를 쓰기를 권장하고 있으니 해당 부분만 보아도 된다.
TestRule
우선 Rule의 동작은 간단하다.
@Before
단계 이전에 실행되며@After
단계가 끝난 후 종료된다.
@Before
, @After
가 없으면 모든 @Test
의 시작과 끝에 맞춰 실행된다.
ActivityRule의 경우로 보면
@Before
단계 이전에 액티비티 Rule이 생성되고@After
단계가 끝난 후 액티비티 Rule이 종료된다.
중요한 건 액티비티 Rule이 실행, 종료되는 거지 액티비티가 그런 것이 아니다.
액티비티는 매 @Test
마다 새롭게 시작하고 종료된다.
ActivityTestRule
가장 기본적인 Rule이다.
생성자
@get:Rule
var activityTestRule: ActivityTestRule<MainActivity> =
ActivityTestRule(MainActivity::class.java)
대상 액티비티만 지정하는 생성자이다.
상단에 @get:Rule
어노테이션을 통해서 해당 Rule을 사용하도록 지정해야 한다.
@get:Rule
var activityTestRule: ActivityTestRule<MainActivity> =
ActivityTestRule(MainActivity::class.java, false, false)
대상 액티비티, 터치 모드, 액티비티 자동 실행 여부를 지정하는 생성자이다.
실행 여부는 true 가 기본값이다.
false로 지정 시 직접 액티비티를 실행시켜주어야 한다. 해당 코드는 뒤에 설명하겠다.
@get:Rule
var activityTestRule: ActivityTestRule<MainActivity> =
ActivityTestRule(
MainActivity::class.java,
"com.onetwothree.espressosample",
Intent.FLAG_ACTIVITY_NEW_TASK,
false,
false
)
대상 액티비티, 패키지명, 실행 플래그, 터치 모드, 액티비티 자동 실행 여부를 지정하는 생성자이다.
패키지명은 해당 액티비티의 패키지명을 넣으면 된다. ( 굳이 왜 생성자로 있는지는 모르겠다. )
사용
ActivityTestRule에서 지원하는 기능은 몇 가지 없다.
실행, ui 스레드 사용, 종료, 액티비티 가져오기, 결과 가져오기 정도이다.
@Before
fun setupData() {
//1
activityTestRule.launchActivity(Intent()
.apply { putExtra("MyArg", "Nothing") })
//2
activityTestRule.runOnUiThread {
//3
activityTestRule.activity.run{
... findViewById 등등 ...
}
}
}
1.
위에서 액티비티 자동 실행을 false로 지정했다면,launchActivity
를 통해서 직접 액티비티를 실행시켜 주어야 한다.
인자로는 Intent를 넘겨야 하며, 필요시에는 데이터를 채워주면 된다.
당연하게도 자동 실행이 true 인 상태에서도 호출해도 된다.
새로운 액티비티가 다시 열릴 뿐이다.
2.
runOnUiThread
함수를 통해 ui thread에 접근할 수 도 있다.
3.
다음으로, 액티비티에 접근하려면 activity
변수를 사용하면 된다.
위의 예제는 액티비티에 접근하여 뷰를 가져오는 ( findViewById
) 작업을 하는 코드이다.
결과 가져오기
@Test
fun resultTest(){
//1
activityTestRule.activity.setResult(Activity.RESULT_OK,Intent().apply {
putExtra("Result", "Ok")
})
//2
activityTestRule.finishActivity()
//3
Assert.assertEquals(activityTestRule.activityResult.resultCode,Activity.RESULT_OK)
val result = activityTestRule.activityResult.resultData.extras?.getString("Result")
Assert.assertEquals(result, "Ok")
}
위의 코드는 액티 비트의 결과를 가져오는 코드이다.
1.
테스트를 위해서 결과를 지정하였다.
2.
결과를 가져오려면 일단, 해당 액티비티를 종료시켜야 한다. ( 실제 과정이랑 동일하게 생각하면 된다.. )
3.
activityResult
변수를 통해서 결과에 접근할 수 있다.
해당 변수는 resultCode
, resultData
를 값으로 가지고 있다.
위의 코드처럼 값을 비교하여 결괏값에 대해서 테스트를 진행하면 된다.
ActivityScenario
ActivityScenarioRule
을 보기 전에 ActivityScenario
을 먼저 보도록 하겠다.
ActivityScenario
은 lifeCycle을 마음대로 바꾸면서 테스트할 수 있도록 해준다.
재생성, 현재 상태 확인, 라이프사이클 이동, 결과 가져오기 등이 가능하다.
ActivityTestRule
의 예제와 동일한 코드를 통해 차이를 알아보자.
생성 & 사용
lateinit var activityScenario: ActivityScenario<MainActivity>
@Before
fun setupData() {
//1.
//activityScenario = ActivityScenario.launch(MainActivity::class.java)
//2.
activityScenario = ActivityScenario.launch<MainActivity>(
Intent(
ApplicationProvider.getApplicationContext(),
MainActivity::class.java
).apply {
putExtra("MyArgs", "Nothing")
})
//3.
activityScenario.onActivity {
(it.findViewById<RecyclerView>(R.id.recyclerView))?.let {
...
}
}
}
Rule
이 아니기 때문에 어노테이션은 필요가 없고,
대신 매번 액티비티를 실행해주는 과정이 필수로 필요하다.
1.launch
에 실행할 액티비티를 넣어주는 방식이다.
만들어진 시나리오를 Test에서 사용하고 싶다면 변수로 들고 있으면 된다.
2.
Intent를 이용하여 생성하는 방식이다.
3.
액티비티에 접근하려면 onActivity
함수를 이용하면 된다.
결과 가져오기
@Test
fun resultTest() {
//1
activityScenario.onActivity {
it.setResult(Activity.RESULT_OK, Intent().apply {
putExtra("Result", "Ok")
})
//2
it.finish()
}
//3
Assert.assertEquals(activityScenario.result.resultCode, Activity.RESULT_OK)
val result = activityScenario.result.resultData?.extras?.getString("Result")
Assert.assertEquals(result, "Ok")
}
1.
테스트를 위해 결괏값을 지정해주었다.
2.
결괏값의 전달을 위해 액티비티를 종료시켰다.
여기서는 꼭 finish()
을 사용해야 한다.
뒤에 나오는 DESTROYED를 통해 앱을 종료시키면 시나리오에 결괏값을 넘기는 단계가 동작하지 않는다.
3.result
변수를 이용하면 결괏값에 대한 정보를 가져올 수 있다.
여기까지는 함수명이나 필드명이 바뀌기만 하고 ActivityTestRule
과 큰 차이가 없다.
액티비티 종료하기
@After
fun close(){
activityScenario.close()
}
테스트 종료 시마다 수동으로 액티비티를 종료해 주어야 한다.
( 옵션이기에 안 해줘도 된다. 하지만 코드 설명에서 "강하게 권장" 이 적혀있으니 잘 돌아가도 넣도록 하자. )
해당 close
함수에서는 앱을 파괴하고, 라이프사이클에 대한 옵저빙을 지우는 과정이 실행된다.
ActivityScenario.launch(MainActivity::class.java).use {
...
}
위의 과정이 귀찮다면 매번 테스트에서ActivityScenario
를 생성하고 use
를 사용하여 자동으로 close 하도록 하면 된다.
( 이게 더 귀찮을 거다. )
LifeCycle 조정하기
@Test
fun moveToStateTest() {
activityScenario.moveToState(Lifecycle.State.STARTED)
Assert.assertEquals(activityScenario.state, Lifecycle.State.STARTED)
activityScenario.moveToState(Lifecycle.State.CREATED)
Assert.assertEquals(activityScenario.state, Lifecycle.State.CREATED)
activityScenario.moveToState(Lifecycle.State.RESUMED)
Assert.assertEquals(activityScenario.state, Lifecycle.State.RESUMED)
activityScenario.moveToState(Lifecycle.State.DESTROYED)
Assert.assertEquals(activityScenario.state, Lifecycle.State.DESTROYED)
activityScenario.recreate()
}
moveToState
를 이용하면 라이프사이클을 바꿀 수가 있다.
INITIALIZED, STARTED, CREATED, RESUMED, DESTROYED 총 5단계를 제공한다.
대신, INITIALIZED는 moveToState 함수의 인자로 사용할 순 없다.
현재 상태는 state
변수를 통해 확인할 수 있다.
recreate
함수를 이용하면 앱을 onSaveInstanceState와 함께 재실행할 수도 있다.
ActivityScenarioRule
ActivityScenarioRule
을 볼 차례이다.
내부에 ActivityScenario
를 변수로 가지고 있으니 기능과 사용법이 동일하다.
생성
@get:Rule
var activityScenarioRule: ActivityScenarioRule<MainActivity> =
ActivityScenarioRule(MainActivity::class.java)
@get:Rule
var activityScenarioRule: ActivityScenarioRule<MainActivity> =
ActivityScenarioRule(
Intent(
ApplicationProvider.getApplicationContext(),
MainActivity::class.java
).apply {
putExtra("MyArgs","Nothing")
}
)
생성 방식은 ActivityScenario
와 동일하다.
대신 Rule
어노테이션을 붙은 게 끝이다.
Rule
이기에 따로 생성과 파괴 과정을 진행할 필요가 없다.
매 테스트마다 자동으로 실행되고 파괴된다.
( 이게 둘의 유일한 차이다. )
사용
@Before
fun setupData() {
activityScenarioRule.scenario.onActivity {
(it.findViewById<RecyclerView>(R.id.recyclerView))?.let {
...
}
}
}
여기도 동일하다.
대신 scenario
변수를 가져와서 onActivity
를 이용하면 된다.
결과 가져오기
@Test
fun resultTest() {
activityScenarioRule.scenario.onActivity {
it.setResult(Activity.RESULT_OK, Intent().apply {
putExtra("Result", "Ok")
})
it.finish()
}
Assert.assertEquals(activityScenarioRule.scenario.result.resultCode, Activity.RESULT_OK)
val result = activityScenarioRule.scenario.result.resultData?.extras?.getString("Result")
Assert.assertEquals(result, "Ok")
}
여기서도 scenario
변수를 가져와서 동일한 과정을 진행하면 된다.
차이?
애매하지만 결과를 가져오는 속도가 현저히 느리며, 시간 초과로 인해 테스트가 실패하는 경우도 있다.ActivityScenario
와 비교하면 코드 차이가 전혀 없는데도, 문제가 발생한다.
( 대신 디버그로 돌릴 시 엄청 빠르다.. 제멋대로다. )
LifeCycle 조정하기
fun moveToStateTest() {
activityScenarioRule.scenario?.let { activityScenario ->
activityScenario.moveToState(Lifecycle.State.STARTED)
Assert.assertEquals(activityScenario.state, Lifecycle.State.STARTED)
activityScenario.moveToState(Lifecycle.State.CREATED)
Assert.assertEquals(activityScenario.state, Lifecycle.State.CREATED)
activityScenario.moveToState(Lifecycle.State.RESUMED)
Assert.assertEquals(activityScenario.state, Lifecycle.State.RESUMED)
activityScenario.moveToState(Lifecycle.State.DESTROYED)
Assert.assertEquals(activityScenario.state, Lifecycle.State.DESTROYED)
activityScenario.recreate()
}
}
추가 적인 설명은 하지 않겠다. ActivityScenario
와 동일하다.
마무리
이번에는 테스트에 필요한 ActivityRule에 대해 알아보았다.
구글이 ActivityScenarioRule
와 ActivityScenario
를 권장하고 있으니 참고하면 좋을 것 같다.
'안드로이드' 카테고리의 다른 글
Android & Java - Reflection (0) | 2020.07.02 |
---|---|
Android & Java - Dynamic Proxy (0) | 2020.06.30 |
Android Espresso #3 - RecyclerView (0) | 2020.06.26 |
Android Espresso #2 - ViewMatcher, ViewAction, ViewAssertion (0) | 2020.06.25 |
Android Espresso #1 - 시작 (0) | 2020.06.24 |