안드로이드 테스팅 입문하기(2)

CmplxN·2020년 10월 18일
0

Testing

목록 보기
2/2

저번 글에 이어서 안드로이드에서 어떻게 테스팅을 하는지 codelabs를 따라하며 배워보자.
저번글에서는 안드로이드 구성요소가 전혀 없는 유닛 테스트를 해봤다.
이번 글에서는 안드로이드 구성요소가 포함된 간단한 유닛 테스트를 해본다.

Hamcrest 사용하기

본격적으로 글을 시작하기 전에 Hamcrest라는 테스팅 라이브러리를 간단히 살펴본다.
Hamcrest를 사용하면 조금더 사람 언어(그래봤자 영어지만)에 가까운 테스트케이스를 작성할 수 있다.
사실 영어권 사람이 아니면 가독성에 크게 공감하지 못할 수 있긴 하다.
하지만 많은 테스트 예제나 실제 테스트들에서 Hamcrest를 사용하므로 Hamcrest를 사용한다.

우선 아래와 같이 App Module의 gradle 파일의 dependencies에 추가한다.

testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"

Hamcrest를 사용하면 아래와 같이 작성할 수 있다.

// default JUnit
assertEquals(result.completedTasksPercent, 0f)

// Hamcrest
assertThat(result.completedTasksPercent, `is`(0f))

즉 앞으로는 I assert that A is (not) B 이런 방식으로 assert문을 작성할 수 있다.
(그냥 is가 아니라 `를 씌운 이유는 코틀린에서 is는 이미 예약어로 쓰이고 있기 때문이다.)

위에서 본 is 말고도 assertThat()에 이어쓸 충분히 많은 함수가 있으므로 Hamcrest Tutorial을 참고하자.

무엇을 테스팅 할 것인가

이번에 테스팅 할 클래스 / 함수는 아래와 같다.

class TasksViewModel(application: Application) : AndroidViewModel(application) {
    private val _newTaskEvent = MutableLiveData<Event<Unit>>()
    val newTaskEvent: LiveData<Event<Unit>> = _newTaskEvent

    fun addNewTask() {
        _newTaskEvent.value = Event(Unit)
    }
}

TasksViewModelAndroidViewModel을 상속한 클래스이기 때문에 Application을 받았다.
addNewTask()는 특별한 기능 없이 새로운 Task가 들어왔다고 이벤트를 전달한다.
여기서 addNewTask를 했을 때 정상적으로 이벤트가 발생하는지 테스팅해보자.

TasksViewModel를 보면 알겠지만 Application이 없으면 아예 생성할 수 없다. 이전 글에서 테스팅한 클래스나 함수는 코틀린 언어만 사용할 줄 할면 만들 수 있는 클래스였다. 하지만, Application클래스는 안드로이드 구성요소로 안드로이드 프레임워크의 도움 없이는 만들 수 없다.

안드로이드 구성요소 가져오기

그러면 테스팅을 위해 안드로이드 구성요소를 사용하는 방법에 대해 알아보자.
AndroidX TestRobolectrics의 관련 의존성을 추가하면 Test 디렉토리에서 simulated Android framework classes를 사용할 수 있다.
(androidTest 디렉터리에서 같은 코드를 실행하면 그때는 실제 class를 사용한다.)

아래와 같이 gradle 파일에 의존성을 추가하자.

testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"
testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"
testImplementation "org.robolectric:robolectric:$robolectricVersion"

이제 테스트 클래스와 테스트 함수를 작성해야하는데 전 글에서의 테스트와 달리 테스트 클래스에 어노테이션을 추가해야한다.

TestRunner 추가하기

먼저 Test 클래스에 @RunWith(AndroidJUnit4::class) 어노테이션을 붙여줘야한다.
사실 JUnit을 사용하는 테스팅은 TestRunner가 필수적으로 필요하다.
이전에 봤던 예제에는 @RunWith어노테이션을 사용하지 않았는데, 이는 기본 TestRunner를 써도 상관없었기 때문이다.

이제 우리는 AndroidXTest를 사용하기 때문에 @RunWith(AndroidJUnit4::class)를 추가한다.
이 어노테이션을 추가함으로써 AndroidXTest는 UI를 포함한 androidTest인지 일반 Test인지 구분하여 작동할 수 있다.

application context 가져오기

그리고 우리가 원했던 Application클래스를 아래와 같이 가져올 수 있다.

ApplicationProvider.getApplicationContext()

이제 생성자의 파라미터 Application를 넘겨 TasksViewModel을 만들 수 있다.
최종적으로 @Test함수의 given을 다음과 같이 작성할 수 있을 것이다.

@Test
fun addNewTask_setsNewTaskEvent() {
    // Given a fresh ViewModel
    val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    // When
    // Then 
}

LiveData 테스팅

사전 작업

LiveDatanewTaskEvent를 테스팅하기 전에 JUnit Rule을 추가한다.
(JUnit Rule은 test 전 / 후에 정의된 코드들이 실행되도록 만든다.)
여기서는 InstantTaskExecutorRule를 사용한다.

InstantTaskExecutorRule을 이용하면 안드로이드 구성요소 관련 작업들을 모두 한 스레드에서 실행되게 한다.
그러므로 모든 작업이 synchronous하게 동작하여 테스팅을 원활하게 할 수 있다.
즉 동기화 때문에 고민할 필요가 없어진다.
특히 LiveData를 이용한다면 필수적으로 InstantTaskExecutorRule를 사용해야할 것이다.

아래와 같이 dependencies를 추가하고.

testImplementation "androidx.arch.core:core-testing:$archTestingVersion"

테스트 코드에 적용하자.

class TasksViewModelTest {
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()
}

LiveData 테스트 작성하기

LiveData를 사용하는 경우 99%의 경우 다음 두 가지가 필요하다.

  1. LifeCycle awareness를 위한 lifecycleOwner
  2. onChanged Action을 위한 Observer
liveData.observe(lifecycleOwner, Observer{ ... })

하지만, 우리가 현재 하려는 테스트에 Activity나 Fragment는 없다.
그러므로 여기서는 LiveData를 lifecycle과 무관하게 쓸 수 있는 observeForever()를 사용한다.
observeForever()를 사용하기 때문에 lifecycle과 무관하게 옵저버 패턴을 사용할 수 있다.

최종적으로는 아래와 같은 테스트 클래스가 될 것이다. (codelabs의 코드다.)

@Test
fun addNewTask_setsNewTaskEvent() {

    // Given : new TasksViewModel
    val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

    try {

        // Observe the LiveData forever
        tasksViewModel.newTaskEvent.observeForever { }

        // When : add a new task
        tasksViewModel.addNewTask()

        // Then : the new task event is triggered
        val value = tasksViewModel.newTaskEvent.value
        assertThat(value?.getContentIfNotHandled(), (not(nullValue())))

    } finally {
        // lifecycle unaware이므로 직접 제거한다.
        tasksViewModel.newTaskEvent.removeObserver(observer)
    }
}

사실 위 코드는 LiveData를 테스팅할 때 마다 등록 / 취소를 해야해서 보일러플레이트 코드가 생긴다.
그래서 codelabs에서는 LiveData<T>.getOrAwaitValue를 사용한다.
함수에 대한 자세한 설명이 필요하면 링크에서 찾아보면 쉽게 이해할 수 있을 것이다.

맺음글

지금까지 안드로이드 구성요소를 포함한 유닛테스트를 작성해봤다.
안드로이드 구성요소들은 개발자가 직접 컨트롤 하기 어렵다.
그러므로 라이브러리의 도움이 없었으면 정말 힘든 작업이었을 것이다.
이제 AndroidX Test Library를 사용해 안드로이드 구성요소를 포함한 유닛테스트도 잘 활용해보자.

다음에는 한 클래스(여기서는 TasksViewModel)만 포함된 테스트가 아닌, 여러 클래스를 복합적으로 테스트해볼 것이다.

profile
Android Developer

0개의 댓글