๊นจ์•Œ ๊ฐœ๋… ๐Ÿ“‘/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 - ๊ธฐ์ดˆ ์ฐธ์กฐ. Local unit test ๋””๋ ‰ํ† ๋ฆฌ์— ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๋ฅผ ๋„ฃ๋Š”๋‹ค.
 

#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