본문 바로가기

Android/TDD

[Mockito] Mockito를 사용해 Instrumented Unit Test를 해보자

728x90

Mockito를 사용하여 로컬 Unit Test는 수행해 보았지만, 계측 Unit Test는 해보지 않아서 한번 적용해 보았다.

 

계측 테스트를 진행하면서 로컬 테스트에서는 발생하지 않았던 이슈들이 발생하였고,

그것을 해결하기 위해서 찾아보고 적용해본 결과를 작성하고자 한다.

 

아직 스터디 중이기 때문에, 다른 방법으로 사용이 가능하다면 추가적으로 글을 작성할 예정이다.


우선, 

계측 테스트를 위해 Gradle 에 라이브러리를 추가해주자.

 

 

Truth 도 함께 사용하기 위하여 같이 추가해 주었다.

이전에 gradle 에 추가한 것과 동일하지만,

 

testImplementation > androidTestImplementation

 

 

계측 테스트는 androidTest 에서 사용되기 때문에 해당 부분에도 적용이 가능하도록 추가해 주었다.

 

test와 androidTest 두 개로 나누어서 선언할 바에는 그냥 implementation으로만 선언하면 되지 않을까?라는 생각을 했는데, 그렇게 선언해놓고 사용하면 빌드 시 오류가 발생한다.

그냥 각각 나눠서 선언하고 나중에 방법을 찾으면 수정하도록 하겠다.

 

라이브러리 설정을 해주고, 간단한 테스트 코드를 작성해 보자.

 

 

MockSampleTest 에서 작업했던 내용을 그대로 옮겨서 사용해 보았는데, 역시나 정상적인 동작은 하지 않고 오류가 발생하였다.

 

org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class com.example.uiteststudy.MockSample
Mockito cannot mock/spy because :
 - final class

 

 

해당 오류는 이전 Mockito 를 로컬 테스트에서 사용하려고 할 때 발생했던 오류이다.

 

따라서 이전과 마찬가지로 inline 에 관련된 항목을 추가해주면 되지 않을까 싶었는데, 추가해보니 다음과 같은 에러가 뜨면서 정상적으로 동작하지 않는다.

 

 

뭔지 모르겠지만 이 방법을 사용하면 안되는 것 같다.

추후, 해당 문제를 해결하는 방법을 찾아보고 적용해볼 예정이지만 지금은 건너뛰도록 하자.

 

그렇다면, inline 을 추가하지 않고 해결하기 위해 Mockito를 사용해 객체를 만들 클래스를 open으로 선언하여 해당 문제를 해결하였다.

 

 

이 상태에서 다시 빌드를 해보자.

 

org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
   Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.

 

안된다.

 

해당 문제를 해결하기 위해 필자는 다양한 방법을 사용해 보았다.

 

1. PowerMock 을 사용
2. dexmaker-mockito-inline 사용
3. MockedStatic 사용
4. 다양한 어노테이션 추가

안된다.

위의 4가지 방법을 사용해 보았을 때 정상적인 동작을 확인할 수 없었다.

 

물론, 위의 것들을 테스트하면서 깊게 파고들어 문제를 해결하기 위해 많은 시간을 투자한 것은 아니기 때문에 각각의 방법으로 해결할 수 없다고 단언할 수는 없다. 단순히 위의 에러를 구글링 하여 나온 것들을 사용해 보았을 때 해결을 할 수 없었다. 정도로 생각하고 넘어가면 될 것이다.

 

그래서 필자는 어떻게 했는가.

Method 에도 open 키워드를 추가해 주었다.

 

open 키워드를 추가하고자 생각한 이유는 다음과 같다.

 

오류를 다시 확인.

1. when(mock.getArticles()).thenReturn(articles); 형태로 호출하라고 하는데, 하고 있다.
2. when 내부에서 mock의 method를 호출하라고 하는데, 하고 있다.
3. final/private/equals()/hashCode() 메서드 중 하나를 stub 하려고 한다.
4. kotlin에서는 기본적으로 final로 선언된다.

 

역시 오류 내용을 꼼꼼히 읽어봐야 하는 것 같다.

 

Java 환경이라면, final을 명시적으로 달아주지 않으면 final 이 되지 않지만 Kotlin 환경에서는 클래스와 메서드는 기본적으로 final로 선언되게 된다.

 

즉, 호출하려고 했던 Mock의 메서드가 final 선언이 되어있기 때문에 호출하는데 오류가 발생했다.

라는 결론에 다다랐다.

 

 

이처럼 테스트에 사용할 class와 method를 모두 open으로 선언해두었더니, 이제야 정상적으로 동작한다.

 

그렇다면 이제 추가적으로 Espresso + Mockito + Truth + Junit4를 사용하여 계측 Unit Test를 수행해 보자.

Espresso, Mockito, Truth를 사용하는 테스트 코드를 작성해 주자.

 

 

이런저런 메서드를 확인해 보기 위하여 테스트 코드를 작성해 보았다.

 

우선, 다양한 테스트 코드에서 정상적으로 테스트가 패스되지 않는 경우 앱은 해당 위치를 실행하다가 앱이 종료된다.

 

애뮬레이터에서 돌아가는 테스트이기 때문에,

UI에 관계없는 테스트는 실패하더라도 이후의 동작은 실행되는 건가 싶어서 확인을 해보았는데, 그것과 상관없이 테스트가 실패하는 순간 앱이 종료된다.

 

여기서 다른 부분은 크게 어려운 것이 없는데, 필자가 헷갈렸던 부분은 Mockito에서 verify를 사용하는 부분이다.

주석에도 추가해 두었지만, Mockito를 제외한 곳에서 사용한다면 그것은 상호 작용으로 카운트하지 않는다.

 

처음 verify를 통해 getNumber()의 카운트 횟수를 체크하는 부분까지 확인해 보자.

 

Mockito.`when`(mock.getNumber()).thenReturn(222)
Mockito.`when`(mock.getString()).thenReturn("100")

assertThat(mock.getNumber()).isEqualTo(222)
Mockito.verify(mock, Mockito.times(1)).getNumber()
Mockito.verify(mock, Mockito.never()).getString()

Espresso.onView(withId(R.id.text1))
	.perform(ViewActions.typeText(mock.getNumber().toString()), ViewActions.closeSoftKeyboard())

Mockito.`when`(mock.getNumber()).thenReturn(369369)
Mockito.verify(mock, Mockito.times(2)).getNumber()

 

필자는 여기서, mock.getNumber()를 호출한 횟수만큼 상호작용했다고 생각하여 체크를 해보았는데 테스트에 통과하지 못하였다.

getNumber()의 카운트 횟수를 확인한 부분을 보면 각각 1,2로 Mockito를 통해서 해당 메서드에 접근하는 경우에만 count 되는 것을 확인할 수 있다.


필자는 계측 테스트에 Mockito 를 적용해보면서, 

로컬 테스트와 계측 테스트의 환경이 상당히 다르구나 라는 것을 다시 한번 깨닫게 되었다.

 

계측 테스트에서 적용하기 위해서 사용해보았던 4가지 방법들은 추후에 다시 확인하여 확실하게 사용해봐야겠다.

 

우선은 아주 간단한 해결 방법인 open 키워드를 사용했지만,

실 프로젝트에서 테스트를 위해 모든 테스트에 사용하는 부분을 open 키워드를 넣어두고 테스트하는 것은 말이 안 되는 게 아닌가? 하는 생각이다.

따라서, 해당 키워드를 제거하고도 사용할 수 있는 방법을 찾아 다시 포스팅을 할 예정이다.

 

 

해당 게시글에 사용된 예제 코드는 Github에 업로드해두었으니 참고 바란다.

https://github.com/HeeGyeong/UnitTestSample

 

GitHub - HeeGyeong/UnitTestSample

Contribute to HeeGyeong/UnitTestSample development by creating an account on GitHub.

github.com

 

728x90