프레그먼트는 앱에서 재사용할 수 있는 컨테이너 역할을 하는 컴포넌트로 프레그먼트를 사용하면 다양한 Activity와 Layout 구성에서 일관된 사용자 인터페이스 레이아웃을 사용자에게 더욱 쉽게 제공할 수 있다. 용도가 다양한 프레그먼트를 고려하면 프래그먼트가 일관되고 리소스 효율적인 환경을 제공하는지 검증하는 것이 중요하다.
프레그먼트 테스트 시에는 AndroidX의 'fragment-testing' 라이브러리를 사용하여 테스트를 진행한다. fragment-testing 라이브러리는 프레그먼트 테스트 시 도움을 받을 수 있는 FragmentScenario 클래스를 지원한다. 해당 클래스는 프레그먼트의 생성과 Lifecycle.State 변경을 지원한다.
2022년 11월 15일 기준 fragment-testing 라이브러리 버전은 아래와 같다. 해당 의존성을 추가하여 준다.
dependencies {
def fragment_version = "1.5.4"
debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
}
fragment-testing 라이브러리를 debugImplementation을 사용하여 추가하는 이유는 테스트 타겟 프로세스에서 FragmentScenario가 종속된 Empty Activity에 액세스할 수 있도록 여기에서는 debugImplementation을 사용한다.
프레그먼트 테스트 (Without Hilt)
프레그먼트 테스트 방법 중 Hilt를 사용하지 않는 방법은 상세하게 다루지 않는다. 따라서 방식에 대해서만 정리한다.
1) 프레그먼트 만들기
앞서 프레스먼트 생성은 fragment-testing 라이브러리의 FragmentScenario 클래스를 사용하여 생성한다고 언급하였다. FragmentScenario는 프레그먼트 생성에 두 가지 방법을 지원한다.
- (1) LaunchInContainer() 함수
: 프레그먼트의 사용자 인터페이스(UI) 테스트 시 사용한다. FragmentScenario는 프레그먼트를 Activity의 RootView Controller에 연결하여 테스트한다. - (2) launch() 함수
: 프레그먼트의 사용자 인터페이스가 포함되지 않는 테스트 시 사용한다. FragmentScenario는 프레그먼트를 RootView가 없는 Empty Activity에 연결하여 테스트한다.
여기서 Empty Activity는 실제 비지니스 로직에서 사용되는 Activity가 아닌 테스트를 위해 프레그먼트만을 모든 기능은 비어있는 액티비티를 생성하여 테스트에 사용한다.
2) LIfeCycle.State 변경하기
FragmentScenario를 이용하여 변경할 수 있는 Lifecycle은 CREATED, STARTED, RESUMED, DESTROYED이다. 변경하는 방법은 간단하다. fragment-testing 라이브러리에서 제공하는 moveToState() 함수를 사용한다. 아래 예제는 안드로이드 개발자 페이지에서 제공하는 간단한 예시이다.
@RunWith(AndroidJUnit4::class)
class MyTestSuite {
@Test fun testEventFragment() {
val scenario = launchFragmentInContainer<EventFragment>(
initialState = Lifecycle.State.INITIALIZED
)
scenario.moveToState(Lifecycle.State.RESUMED)
}
}
프레그먼트 테스트 (With Hilt)
Hilt를 프레그먼트 테스트 시에 적용하기 위해서는 위에서 launchFragmentInContainer() 는 사용하지 못한다. 대신 launchFragmentInHiltContainer() 를 사용하여 테스트를 진행한다.
테스트를 위한 순서를 간략히 정리한다.
1) src/debug 경로 생성하기
src 디렉토리 하위에 debug 디렉토리를 생성한다. 이 경로에는 테스트를 위해 Empty Activity를 생성할 것이다. 이 경로는 debug 시에만 사용되도록 한다.
2) 테스트 액티비티 추가 (Empty Activity)
위에서 생성한 debug 경로 아래 기존 프로젝트와 동일 패키지로 생성한다. 생성 후 테스트 액티비티를 추가한다.
package com.linuxias.setup_for_testing
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class HiltTestActivity : AppCompatActivity()
3) Manifest 파일 추가
테스트 시에 기존의 액티비티가 아닌 테스트 액티비티를 타겟에서 사용하도록 설정해줘야 한다. src/debug/ 경로 아래에 Manefest 파일을 아래와 같이 추가한다.
(주의. 안드로이드 스튜디오에서 프로젝트를 안드로이드 기반으로 보게되면, 해당 Manifest 파일은 파일트리에 표시되지 않는다.)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".HiltTestActivity"
android:exported="false" />
</application>
</manifest>
4) 테스트 러너 추가하기.
Hilt를 안드로이드 프로젝트에 적용하기 위해서 Hilt를 사용하는 모든 앱은 @HiltAndroidApp으로 주석이 지정된 어플리케이션 클래스를 포함해야 한다.
@HiltAndroidApp은 애플리케이션 수준 종속 항목 컨테이너 역할을 하는 애플리케이션의 기본 클래스를 비롯하여 Hilt의 코드 생성을 트리거한다.
테스트에서는 @HiltAndroidApp 애노테이션을 사용할 수는 없다. 그래서 아래와 같은 방식으로 AndroidJUnitRunner를 상속받은 클래스에서 newApplication() 함수를 오버라이드한다.
오버라이드 한 함수에서 super.newApplication에서 HiltTestApplication을 전달함으로써 @HiltAndroidApp과 동일한 역할을 수행할 수 있도록 한다.
class HiltTestRunner : AndroidJUnitRunner() {
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}
추가로 이 어플리케이션이 Test를 위함임을 build.gradle에 아래와 같이 명시해야 한다. 명시하는 위치는 defaultConfig 내부에 testInstrumentationRunner에 설정한다.
defaultConfig {
applicationId "com.linuxias.setup_for_testing"
minSdk 30
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "com.linuxias.setup_for_testing.HiltTestRunner"
}
5) launchFragmentInHiltContainer() 코드 추가하기
Hilt와 함께 androidx.fragment:fragment-testing 라이브러리의 launchFragmentInContainer를 사용할 수 없다. 이 코드는 @AndroidEntryPoint로 주석이 지정되지 않은 활동에 의존하기 때문이다. 따라서 아래 코드를 사용한다.
(안드로이드 팀 제공 architecture-samples - launchFragmentInHiltContainer)
@ExperimentalCoroutinesApi
inline fun <reified T : Fragment> launchFragmentInHiltContainer(
fragmentArgs: Bundle? = null,
@StyleRes themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
crossinline action: Fragment.() -> Unit = {}
) {
val startActivityIntent = Intent.makeMainActivity(
ComponentName(
ApplicationProvider.getApplicationContext(),
HiltTestActivity::class.java
)
).putExtra(
"androidx.fragment.app.testing.FragmentScenario.EmptyFragmentActivity.THEME_EXTRAS_BUNDLE_KEY",
themeResId
)
ActivityScenario.launch<HiltTestActivity>(startActivityIntent).onActivity { activity ->
val fragment: Fragment = activity.supportFragmentManager.fragmentFactory.instantiate(
Preconditions.checkNotNull(T::class.java.classLoader),
T::class.java.name
)
fragment.arguments = fragmentArgs
activity.supportFragmentManager
.beginTransaction()
.add(android.R.id.content, fragment, "")
.commitNow()
fragment.action()
}
6) 테스트 코드에서 프레그먼트 생성하기
launchFragmentInHiltContainer를 사용하여 Fragment를 생성하는 간단한 코드이다. 이렇게 프레그먼트를 생성한 후 테스트 코드를 작성하면 된다. moveToState() 를 통해 LifeCycle.State를 변경할수도 있으며, UI 관련 테스트도 작성할 수 있다.
@Test
fun testLaunchFragmentInHiltContainer() {
launchFragmentInHiltContainer<AddMarsPhotoItemFragment> { }
}
레퍼런스
- architecture-samples launchFragmentInHiltContainer
- https://developer.android.com/guide/fragments/test
- https://developer.android.com/training/dependency-injection/hilt-testing
'Android > Testing' 카테고리의 다른 글
[Android/Testing] #10. Fragment Navigation Test (0) | 2022.11.18 |
---|---|
[Android/Testing] #8. 테스트에 Dagger-Hilt 적용해보기 (0) | 2022.11.08 |
[Android/Testing] #7. ViewModel 테스트하기 (with Fake Repository) (0) | 2022.10.22 |
[Android/Testing] #6. 테스트 시작 전에 테스트 더블(Test Double) 이해하기 (0) | 2022.10.08 |
[Android/Testing] #5. 테스트를 위한 기반 프로젝트 생성하기 (0) | 2022.10.02 |