안드로이드 연구소

TestCode 간단한 예제(feat. Mockito) 본문

안드로이드 연구소/테스트코드

TestCode 간단한 예제(feat. Mockito)

안드로이드 연구원 2023. 8. 14. 18:09

테스트 코드 중 UnitTest와 Mockito에 대한 설명은 아래 링크에서 확인하세요.

 

[연봉 5000 넘는 안드로이드 개발자 되는 법] Unit테스트란(ViewModel에서 Unit테스트하기)

저번 강의에서 안드로이드 테스트코드의 종류들을 살펴보았습니다. 그 중 첫번째 핵심 테스트인 Unit테스트를 시작해보겠습니다. Q1. 안드로이드에서 사용한 Unit테스트에 대해서 설명해줘. 안드

android-lab.tistory.com

 

1. 환경 설정

build.gradle에 테스트코드 라이브러리 mockito 설치

dependencies {
	testImplementation 'org.mockito:mockito-inline:5.0.0'
	testImplementation 'androidx.arch.core:core-testing:2.1.0'
}

 

또 build.gradle 위에서 UnitTest를 할 수 있도록 설정

android {
    testOptions {
        unitTests.returnDefaultValues = true
    }
}

 

2. 테스트 코드 파일 생성

ViewModel 파일의 테스트 코드를 작성해볼 예정입니다.

해당 파일의 이름에 option + Enter를 누르시면 아래처럼 여러 항목들이 나오는데

"Create test"를 누르시면 됩니다.

 

그러면 아래와 같이 나오는데 JUnit4로 설정하고

해당 클래스에서 테스트할 함수를 클릭하면 됩니다.(그냥 빈함수만 만들어줍니다.)

 

그렇게하면 테스트 코드 파일이 생성이되는데 

"앱이름"(test) 이 부분에 생성됩니다.

 

3. 테스트 코드 작성하기(1)

그렇다면 이제 실제 테스트 코드를 작성을 해보면 

@RunWith(MockitoJUnitRunner::class) // (1)
class SearchViewModelTest {

    // (2) Rule 설정
    @Rule 
    @JvmField 
    var rule = InstantTaskExecutorRule() 

    // (3) Mock객체 생성
    @Mock
    lateinit var searchRepository: SearchRepository

    @Mock
    lateinit var mockLoadingObserver: Observer<Boolean>
    
    @Mock
    lateinit var mockListObserver: Observer<List<ListItem>>

    private lateinit var viewModel: SearchViewModel
    
    // (4) 실제 테스트가 실행되는 @Test 어노테이션 실행 전 먼저 실행되는 부분
    @Before
    fun setUp() {
        viewModel = SearchViewModel(searchRepository)
        viewModel.showLoading.observeForever(mockLoadingObserver)
        viewModel.listLiveData.observeForever(mockListObserver)
    }
}

(1)에서 Mockito로 테스트할 것을 선언 후

(2)에서 테스트 규칙 생성합니다.

 

(3) 그리고 ViewModel에서 연결할 때 필요한 인스턴스들을 mock 가짜 객체로 선언합니다.

 

(4) @Test가 실행전 @Before에서 viewModel에서 만들어 둔 mock객체들과 연결합니다

 

 

4. 테스트 코드 작성하기(2)

// (2) 실제 테스트 코드 작업이 일어나는 @Test 부분 
@Test
fun searchNotEmptyList() {
    // (3) 테스트할 메서드 호출 시에 필요한 데이터 가짜로 선언
    Mockito.`when`(searchRepository.search(anyString())).thenReturn(Observable.just(mockList()))
    
    // (4) 테스트할 메서드 호출
    viewModel.search(anyString())

    // (5) 호출 횟수 검증 부분
    Mockito.verify(mockLoadingObserver, Mockito.times(1)).onChanged(true)
    Mockito.verify(mockListObserver, Mockito.times(1)).onChanged(Mockito.anyList())
    
    // (6) JUnit검증 부분
    assertTrue(!viewModel.listLiveData.value.isNullOrEmpty())
}

// (2) 실제 테스트 코드 작업이 일어나는 @Test 부분
@Test
fun searchEmptyList() {
    Mockito.`when`(searchRepository.search(anyString())).thenReturn(Observable.just(emptyList()))
    viewModel.search(anyString())

    Mockito.verify(mockLoadingObserver, Mockito.times(1)).onChanged(true)
    Mockito.verify(mockListObserver, Mockito.times(1)).onChanged(Mockito.anyList())
    assertTrue(viewModel.listLiveData.value.isNullOrEmpty())
}

// (1)리스트에서 사용될 데이터를 가짜 데이터로 생성
private fun mockList() = listOf(
    ImageItem("thumbnailUrl", "collection", "siteName", "docUrl", Date(), false),
    ImageItem("thumbnailUrl", "collection", "siteName", "docUrl", Date(), true),
    VideoItem("thumbnailUrl", "title", 3, "author", Date(), false)
)

(1)에서 가짜 리스트 데이터를 생성한 후에

(2)의 @Test어노테이션 안에서 실제 테스트를 할 내용을 호출하고 검증합니다.

 

(3)에서 ViewModel의 search함수를 호출하기 위해서 필요한

searchRepository.search("String") 호출해서 가져올 데이터를 

Mockito.`when`(A).thenReturn(B)를 통해서 A를 선언할 때 B가 반환되게 선언합니다.

즉 여기서는 searchRepository.search(anyString())을 사용하면 (1)에서 만들어둔 리스트가 반환되는다는 얘기입니다.

 

(4)에서 테스트할 메서드를 호출합니다.

 

(5)을 통해서 올바른 결과가 출력되는지를 검증합니다.

아래는 실제 ViewModel에서 테스트할 search메서드가 적혀있는 부분입니다.

해당 메서드가 실행되면 showLoading 데이터와 listLiveData의 값이 변화하게됩니다.

class SearchViewModel(private val searchRepository: SearchRepository) : ViewModel() {
    // (1) search메서드 호출시 변화되는 데이터1    
    private val _listLiveData = MutableLiveData<List<ListItem>>()
    val listLiveData: LiveData<List<ListItem>> get() = _listLiveData
    
    // (2) search메서드 호출시 변화되는 데이터2  
    private val _showLoading = MutableLiveData<Boolean>()
    val showLoading: LiveData<Boolean> get() = _showLoading

    // (3) 테스트할 실제 메서드    
    fun search(query: String) {
        disposable?.add(searchRepository.search(query)
            .doOnSubscribe { _showLoading.value = true }
            .doOnTerminate { _showLoading.value = false }
            .subscribe({ list ->
                _listLiveData.value = list
            }, {
                _listLiveData.value = emptyList()
            })
        )
    }
}

그렇기 때문에 두 LiveData가 정확하게 호출되었는지

데이터가 원하는 데이터로 바뀌었는지를 검증해야합니다.

Mockito.verify(mockLoadingObserver, Mockito.times(1)).onChanged(true)
Mockito.verify(mockListObserver, Mockito.times(1)).onChanged(Mockito.anyList())

해당 검증 메서드를 통해서 mockLoadingObserver의 onChanged(true) 내장 함수가 1번 호출되었는지를 검사합니다.

또 mockListObserver의 onChanged()함수가 한번 실행되었는지도 검사를 합니다.

만약 이 중 하나라도 원하는 한번도 실행되지 않았거나 두번 이상 실행되었다면 검사는 실패하게 됩니다.

 

(6) 마지막으로 해당 변경된 listLiveData가 viewModel에도 잘 저장이 되었는지 그대로 빈 값인지를 테스트합니다.

 

 

5. 테스트 코드 실행하기

이렇게 테스트 코드를 전부 작성하면

@Test 어노테이션 밑에 실행을 할 수 있습니다.

 

Comments