ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Android] Koin Test로 테스트 코드 작성하기
    Mobile Engineering 2020. 5. 12. 23:26

    이 글은 이미 Koin 세팅을 하셨다는 전제하에 기술하였습니다. 아직 Koin을 세팅하지 않았다면 이전 세팅 방법에 대해서 포스팅을 참고하시기 바랍니다.

    https://simplifyprocess.tistory.com/5

     

    이번에는 Koin을 사용해서 테스트 코드를 작성할때 사용하는 KoinTest에 대해서 기술하고자 합니다.

     

    Gradle 세팅

    일단 어느 라이브러리를 사용하던지 gradle 세팅을 안할 수는 없겠죠. 이부분도 아주 간단합니다.

    먼저 다음과 같이 MavenRepository에 jCenter를 추가합니다.

    // build.gradle at Project Level
    
    buildscript {
    	repositories {
    		jcenter()
    	}
    }

     

    그리고 Module Gradle에 KoinTest를 추가합니다.

    // build.gradle at Module
    
    dependencies {
    	testImplementation "org.koin:koin-test:$koin_version"
    }

    위 두가지 항목을 추가하셨으면 KoinTest를 사용하기 위한 준비는 끝났습니다.

     

    Prepare UnitTest

    Koin의 공식 사이트에서는 UnitTest 코드에 Koin Module과 Mockito를 로딩하는 코드가 함께 기재되어 있습니다. 그런데 UnitTest 코드를 작성할때마다 모듈을 로딩하도록 하면 테스트 코드가 중복 코드로 넘쳐날 것입니다. 그래서 추상 객체를 하나 만들어서 테스트 코드들이 상속을 받도록 하였습니다.

    // AbstractKoinTest.kt
    
    package com.hans.android.koinexam
    
    import com.hans.android.koinexam.di.myModule
    import org.junit.Rule
    import org.koin.core.logger.Level
    import org.koin.test.KoinTest
    import org.koin.test.KoinTestRule
    import org.koin.test.mock.MockProviderRule
    import org.mockito.Mockito
    
    abstract class AbstractKoinTest: KoinTest {
        @get:Rule
        val koinTestRule = KoinTestRule.create {
            printLogger(Level.DEBUG)
            modules(myModule)
        }
    
        @get:Rule
        val mockProvider = MockProviderRule.create {
                clazz -> Mockito.mock(clazz.java)
        }
    }

    이렇게 만들면 테스트 코드는 Koin Module 로딩은 신경쓰지 않아도 됩니다.

     

    Test Code

    그럼, 간단한 메소드 하나에 대해서 테스트 코드를 만들어 보겠습니다.

    // CityUseCase.kt
    package com.hans.android.koinexam.domain.usecase
    
    import com.hans.android.koinexam.data.repository.CityRepository
    
    class CityUseCase(private val cityRepository: CityRepository) {
    
        fun getCities(): List<String> {
            return cityRepository.getCities()
        }
    
        fun getCity(name: String): String {
            return cityRepository.getCity(name)
        }
    
        fun isExistsCity(name: String): Boolean {
            return cityRepository.isExistsCity(name)
        }
    }

    위처럼 CityUseCase는 CityReposity를 주입 받아서 비즈니스 로직을 수행합니다. isExistsCity()에 대한 테스트 코드를 작성해 보겠습니다.

    //CityUseCaseTest.kt
    package com.hans.android.koinexam.domain.usecase
    
    import com.google.common.truth.Truth.assertThat
    import com.hans.android.koinexam.AbstractKoinTest
    import com.hans.android.koinexam.data.repository.CityRepository
    import org.junit.Test
    import org.koin.test.inject
    import org.koin.test.mock.declareMock
    import org.mockito.ArgumentMatchers.anyString
    import org.mockito.BDDMockito.given
    
    class CityUseCaseTest: AbstractKoinTest() {
    
        val cityUseCase: CityUseCase by inject()
    
        @Test
        fun isExistsCity() {
            val cityRepository = declareMock<CityRepository> {
                given(isExistsCity(anyString())).willReturn(true)
            }
    
            assertThat(cityUseCase.isExistsCity("Seoul")).isTrue()
        }
    }

    CityUseCaseTest라는 객체는 AbstractKoinTest를 상속 받아서 KoinTest를 세팅해주고, cityUseCase는 'by inject()'로 InjectMock 객체로 선언합니다. CityUseCase에서 주입 받는 CityRepository는 declareMock을 이용해서 Mock 객체로 선언해 주고 BDD를 이용해서 given으로 리턴값을 지정합니다. 이렇게 해서 테스트 코드를 실행하면 아래와 같이 성공! 짜잔~

     

    Conclusion

    아주 간단 명료하게 설명을 드렸지만, DI와 Mock을 이용해서 테스트 코드를 작성한다는 것은 아주 심오한 개념이 밑바탕에 깔려있습니다. 그것은 바로 '관심사의 분리 Separation of Concern'입니다. CityUserCaseTest.kt의 테스트 코드에서 CityRepository의 값은 DI Mock을 이용해서 무엇을 리턴하던지 상관없는 블랙박스로 만들고, CityRepository 이외의 코드에 대해서 검증을 하는 것입니다. 이렇게 블랙박스 레벨을 어느정도 가져갈 것인가를 고민하고 설계에 반영하면 Dagger와 Koin 모두 쾌적한 TDD 개발을 실천할 수가 있습니다. 하지만 많은 안드로이드 개발자들이 DI 라이브러리를 사용하면서 충분히 테스트코드를 작성할 수 있는 환경임에도 불구하고 Presenter, ViewModel에 엄청난 비즈니스 로직을 넣어서 TDD는 커녕 뒤엉킨 코드로 힘겹게 업무를 보는걸 보면 안타까울 뿐입니다. (실제로 현업에서 Presenter 하나가 3,000 Line이 넘는걸 보고 경악을 금치 못했습니다. 뜨헉..o0o) 사실 진짜 문제는 MVP, MVVM 패턴들을 맹신하면서 한쪽 면만을 보는 것이 더 심각한 문제일 것입니다. 모바일 개발 패러다임에 대한 제 견해는 다른 포스팅에서 이야기하겠습니다. 다시 KoinTest로 돌아와서 Koin의 컨셉과 아주 잘 들어맞도록 어떠한 세팅이나 준비를 위한 리서치가 필요없을 정도로 간단하게 사용할 수 있었습니다. 이렇게 테스트 코드에 집중할 수 있도록 간편하게 환경을 만들 수 있는 Koin을 만드신 개발자들에게 감사의 인사를 드립니다.

     

    위 코드는 아래의 Github에서 확인하실 수 있습니다.

    https://github.com/goldfing/AndroidProjects/tree/master/KoinExam

     

    Thanks

    Hans

     

Designed by Tistory.