Android

MockK 를 사용해서 테스트 코드를 적어보자

최데브 2023. 5. 14. 14:30

휴일이지만 일 때문에 어쩌다보니 일 아닌 일을 하고 있는데

시간도 많이 남고해서 미뤄놨던 테스트 코드나 적어볼까 하고 뒤적뒤적 하는중

코틀린용 mock 테스트 라이브러리 mockK를 발견하고 테스트 코드를 끄적여봤다.

 

기존에 자바 진영에서 많이 쓰던 mockito 가 대표적인 mock 라이브러리지만 mockK도

코틀린에 좀 더 적합한 방식을 제공하는것뿐 전체적인 매커니즘은 크게 다르지 않았다.

 

아 참, 이 글을 읽으러 누추한 블로그까지 찾아오셨다면 mock 에 대해서 알고 있는 분일거라 생각하지만

mock 은 모의객체를 의미한다. 풀어서 말하면 실제로 내부 코드들이 전부 작성되어 작동하는게 아닌 껍데기뿐인 객체라는 말이다.

 

이런건 왜 필요할까?

귀찮고 불편해서다.

테스트 코드를 작성하다보면 안드로이드의 경우는 기기 종속적인 상황도 종종 등장해서 테스트 코드를 작성하기에 아주

곤란한 상황이 생기기도 하고 내가 검증하고자 하는것 외에 것들이 코드적으로 엮이고 종속되어 있어서 코드가 돌아가게 하려면 내가 테스트 하고 싶은 일부분을 위해 전부를 다 테스트 코드에 알려줘야하는 복잡하고 머리 아픈 일이 생긴다.

 

이런것들을 '아! 일단 있다치고!'  

로 만들어주는게 Mock 이라는 개념이다.

그래그래 껍데기를 넣어서 대충 코드는 돌아가게 했다 치자 그럼 내가 원하는대로 값을 넣는것도 껍데기니까 실제 값을 뱉어내지도 못할꺼 아냐?

세상의 프로그래머들은 아주 똑똑하기 때문에 다 방법을 마련해뒀다.

 

 mock 라이브러리마다 조금씩 다르게 사용하는거 같지만 이번엔 mockK에 대해서 설명하고 있으니 해당 방법만 적어보겠다.

 

일단 전체 코드를 보고 하나씩 살펴보자.

 

@ExperimentalCoroutinesApi
class ExampleUnitTest {

    @get:Rule
    val coroutineRule = CoroutineRule()

  @Test
    fun ready_display_test() = runTest(coroutineRule.testDispatcher){
        val repository: AdminRepository = mockk()
        val viewModel = AdminViewModel(repository)

        coEvery {
            repository.getReadyDisplay("AA","BB")
        } returns flowOf(NetworkState.Loading)

        viewModel.getReadyDisplay()

        coVerify {
            repository.getReadyDisplay("AA","BB")
        }

    }
}

 

위 코드는 viewModel 의 동작을 검증하기 위해 작성한 코드다.

검증하고자 하는 객체는 실제 객체를 선언해주고 실제 객체와 의존성이 있는 요소들을 mock 으로 만들어준다.

실제 검증하고자 하는 객체까지 mock 으로 하면 다 가짜로 테스트를 진행하는것이니 의미있는 결과를 얻을 수 없을지도 모른다.

 

나의 경우는 AdminViewModel 이 아래와 같이 작성되어있다.

@HiltViewModel
class AdminViewModel @Inject constructor(
    private val adminRepository: AdminRepository
) : ViewModel() {
	/// 중략
}

viewModel 을 테스트하기 위해 주입되어있는 amdinRepository 를 mock 으로 처리해서 간편하게 테스트를 진행할 수 있게 된다.

 

그 아래에는 coEvery 라는걸 볼수가 있는데

mockk에서 모의객체의 행동을 정의해주는거라고 할 수 있다.

coEvery는  suspend로 만들어진 중단함수의 행동을 정의할때 사용하고 기본형으로는 every로 사용한다.

        coEvery {
            repository.getReadyDisplay("AA","BB")
        } returns flowOf(NetworkState.Loading)

coEvery로 감싸고 내부에 검증하고자 하는 mock 객체의 동작을 작성한다.

그리고 동작하고나서 어떤 결과값을 받았는지를 retruns 로 정의해준다.

이런 input 으로 실행을 했을때 이런 output이 나오는 상황을 테스트 할거야 라는 뜻이 되겠다.

(중단함수의 지원을 하고 안하고 차이일뿐 every도 동일한 방식으로 사용한다.)

 

그리고 그 다음줄이 실제객체인 viewModel 의 getReadyDisplay() 라는 함수를 실행시키고

 

coVerify는 해당 함수가 정상적으로 호출이 되었는지를 확인하는 명령어다.

co가 붙은것처럼 이것도 중단함수를 지원한다. 아닌 경우에는 그냥 Verify 로 사용해주면 된다.

 

verify에는 함수들이 순서대로 실행됐는지 확인하는 verifyOrder 나  순서 상관없이 모두 수행됐는지 확인하는verifySequence 등 여러가지 기능들이 있는데 필요할때 찾아보고 익혀두면 좋을듯하다.

 

참고로 

viewModel.getReadyDisplay()

가 먼저 불리고 

coEvery {
    repository.getReadyDisplay("AA","BB")
} returns flowOf(NetworkState.Loading)

가 오면 오류가 난다. 실행전에 먼저 mock 객체의 행동을 정의해주지 않았기 때문이다.

 

이러고 테스트를 돌려보면 테스트 성공으로 나온다

repository의 함수가 호출이 잘되는지 검증이 됐다!

반응형