깨알 개념/Android

[Android] Unit Testing - ViewModel

interfacer_han 2024. 7. 8. 11:50

#1 이전 글

#1-1 Unit Testing 개요

 

[Android] Unit Testing - 개요와 환경 설정

#1 안드로이드 앱 테스트#1-1 안드로이드 앱 테스트의 종류먼저, 여기에 있는 구글 공식 문서에서 안드로이드 앱 테스트에 대한 개요를 읽으면 좋다. 해당 구글 공식 문서에서 복사해온 위의 그림

kenel.tistory.com

위 링크에 있는 이전 게시글에 이어서, 실제 안드로이드 프로젝트를 만들어 ViewModel의 Unit Testing을 수행해본다. 본 게시글을 읽기 전에 [Android] Unit Testing - 기초를 보고 오면 이해에 도움이 된다.
 

#1-2 환경 설정 (build.gradle 등)

이전 게시글의 #3을 토대로 본 게시글에 나오는 안드로이드 프로젝트의 Gradle, AGP, JDK의 버전 설정 및 build.gradle 설정을 진행한다. 이전 게시글의 build.gradle과 달리, 본 게시글에서는 Unit Testing에 사용되지 않는 build.gradle의 plugins { ... }, buildFeatures { ... }, dependencies { ... }의 일부 요소를 제거했다. 이는  코드 다이어트를 위한 개인적인 제거이기 때문에, 이 글은 보는 사람은 제거 없이 그냥 복사 및 붙여넣기해도 된다.
 

#2 Unit Testing을 적용할 샘플 앱

전체 소스 코드는 본 게시글의 #5에 있다.
 

#2-1 개요

하나의 액티비티에 하나의 뷰모델로 구성한, 아주 간단한 계산기 앱이다. 
 

#2-2 interface Calculation

interface Calculation {

    fun sum(operand1: Int, operand2: Int): Int

    fun multiply(operand1: Int, operand2: Int): Int

    fun sumAndSquare(operand1: Int, operand2: Int): Int
}

Test double이 상속받아 구현할 인터페이스다.
 

#2-3 class CalculationViewModel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class CalculationViewModel(private val calculation: Calculation) : ViewModel() {
    private val _operand1: MutableLiveData<Int> = MutableLiveData(0)
    val operand1: MutableLiveData<Int>
        get() = _operand1

    private val _operand2: MutableLiveData<Int> = MutableLiveData(0)
    val operand2: MutableLiveData<Int>
        get() = _operand2

    private val _result: MutableLiveData<Int> = MutableLiveData(0)
    val result: LiveData<Int>
        get() = _result

    fun onSumClicked() {
        _result.value = calculation.sum(_operand1.value!!, _operand2.value!!)
    }

    fun onMultiplyClicked() {
        _result.value = calculation.multiply(_operand1.value!!, _operand2.value!!)
    }

    fun onSumAndSquareClicked() {
        _result.value = calculation.sumAndSquare(_operand1.value!!, _operand2.value!!)
    }
}

변수 operand1 및 operand2는 #2-1에 있는 2개의 EditText와 바인딩된다. 또, 3개의 메소드 또한 #2-1에 있는 3개의 Button과 바인딩되어 해당 버튼이 클릭될 때 실행된다. 마지막으로 result는 결과를 표시할 TextView와 바인딩된다.
 

#3 Unit Testing

#3-1 간편하게 테스트 클래스를 생성하는 방법

[Android] Unit Testing - 기초 참조
 

#3-2 테스트 클래스의 내용 작성

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.google.common.truth.Truth
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito

class CalculationViewModelTest {

    /* InstantTaskExecutorRule 규칙(Rule)
     * 기본적으로 백그라운드 스레드에서 실행되는 LiveData를 메인 스레드에서 실행되도록 강제함.
     * LiveData가 백그라운드 스레드에서 그 값이 변경된다면, 메인 스레드에서 변화를 감지하지 못할 수도 있기 때문.
     * 또, 데이터의 변화를 감지하기 전에 테스트가 끝나버릴 여지도 있음.
     * 따라서, LiveData가 관여되는 테스트에는 InstantTaskExecutorRule 규칙이 많이 사용됨.
     */
    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()

    private lateinit var calculation: Calculation
    private lateinit var calculationViewModel: CalculationViewModel

    @Before
    fun setUp() {
        // Dependency (Test double) 초기화
        calculation = Mockito.mock(Calculation::class.java)
        Mockito.`when`(calculation.sum(2, 3)).thenReturn(5)
        Mockito.`when`(calculation.multiply(5, 4)).thenReturn(20)
        Mockito.`when`(calculation.sumAndSquare(4, 7)).thenReturn(121)

        // Dependent 초기화
        calculationViewModel = CalculationViewModel(calculation)
    }

    @Test
    fun onSumClicked_withOperands2And3_updateLiveData() { // subjectUnderTest_actionOrInput_resultState 규칙으로 함수 명명함
        calculationViewModel.operand1.value = 2
        calculationViewModel.operand2.value = 3
        calculationViewModel.onSumClicked()
        val result = calculationViewModel.result.value
        Truth.assertThat(result).isEqualTo(/* expected = */ 5)
    }

    @Test
    fun onMultiplyClicked_withOperands5And4_updateLiveData() {
        calculationViewModel.operand1.value = 5
        calculationViewModel.operand2.value = 4
        calculationViewModel.onMultiplyClicked()
        val result = calculationViewModel.result.value
        Truth.assertThat(result).isEqualTo(/* expected = */ 20)
    }

    @Test
    fun onSumAndSquareClicked_withOperands4And7_updateLiveData() {
        calculationViewModel.operand1.value = 4
        calculationViewModel.operand2.value = 7
        calculationViewModel.onSumAndSquareClicked()
        val result = calculationViewModel.result.value
        Truth.assertThat(result).isEqualTo(/* expected = */ 121)
    }
}

Test double의 일종인 Mock을 생성해주는 Mokito 라이브러리를 이용했다.
 

#4 작동 확인

 

#5 완성된 앱

 

android-practice/unit-test/TestingViewModel at master · Kanmanemone/android-practice

Contribute to Kanmanemone/android-practice development by creating an account on GitHub.

github.com