Android 공부/Android Library Study

테스트 코드로 Retrofit 테스트 해보기

Junit4으로 Retrofit 테스트 해보기



0. 주저리 주저리

예전부터 해보고 싶은 것중 하나는 테스트 코드를 작성해보고 싶었고, 그것을 빌미로 프로젝트를 진행해보고 싶었다. 이런 방법을 TDD라고 하는 것 같았고, 항상 그런 정보를 접할 때마다 공부를 하고 싶었다. 그래서, TDD 방법론까지는 아니지만, 테스트 코드를 맛보기 위해서


RxJava + Retrofit2 => Test based Junit4



으로 작성된 테스트 코드를 만들어보려고 한다.



1. 1단 해야할 것들

1-1. 참고 사이트

보고 진행한 사이트는 AndroidDeveloper와 vogella의 tutorial입니다.
우선, Gradle은 건드릴 것도 없이 기본적으로 사용되어지는 코드들을 그대로 사용하고 있습니다. 추가적으로 모듈을 사용할 때마다, 글에서 다루도록 하겠습니다.

1
2
3
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
cs


2. 안드로이드를 실행할 때마다, 봤던 Test Folder들은 무엇일까?



 어플리케이션을 만들 때마다, androidTest, test와 같은 폴더들이 만들어지고, 우리는 평상시와 똑같이 녹색 부분을 감춘채, MainActivity를 코딩하기 시작합니다. 그런데, 신기한게 이러한 폴더들이 특정 Test를 실행하기 위해서 구분되었다고 합니다.


[각주:1]

 경로

역할 

 app/src/main

  Main Application이 빌드되는 위치

 app/src/test

  JVM에서 Unit Test를 할 수 있는 위치

 app/src/androidTest

  Android device에서 Test할 수 있는 위치


우리가 항상 코드를 작성하는 app/src/main에서는 Application이 빌되는 위치이며, 그 외에 Test의 분류는 JVM단에서 Unit Test를 하는 것Android device에서 물리적으로 Test할 수 있는 2가지 테스트가 있다는 것을 알 수 있습니다.


3. 시작이 절반이다.

Test code는 3가지의 순서로 실행된다고 합니다. @Befroe, @Test, @After의 순서로 진행되며, @Test가 실행되기 이전과 이후에 항상 @Before@After가 호출된다고 합니다.

3-1. app/src/main/CalculatorUtil.kt

A와 B를 더하는 단순한 작업
1
2
3
class CalculatorUtil {
    fun plus(a:Int, b:Int) : Int = a+b
}
cs

3-2. app/src/test/ExampleUnitTest.kt

@Before : Start를 print하고, calculator 객체를 만드는 로직
@Test : 두 값을 plus하고, 값이 정확한지 비교하는 로직
@After : finish를 print

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ExampleUnitTest {
    private lateinit var calculatorUtil : CalculatorUtil
    @Before fun initialize(){
        println("Start")
        calculatorUtil = CalculatorUtil()
    }
 
    @Test fun plus(){
        val sum = calculatorUtil.plus(10,10)
        assertEquals(sum, 20)
    }
 
    @After fun finish(){
        println("Finish")
    }
}
cs

4. 생각보다 자유롭지 않은 Test Code

4-1. Test로 Log 찍기

안녕하세요()를 통해서 Log를 찍으려고 합니다. 

1
2
3
4
5
6
class ExampleUnitTest {
    @Test
    fun 안녕하세요(){
        Log.d("hello","hello")
    }
}
cs


아래와 같이 Method d가 mocked 되지 않았다면서, 오류를 출력하는 것을 볼 수 있었습니다. 그러한 이유는 static과 같은 싱글톤은 Test Code를 힘들다는 얘기를 들었는데, Log와 같은 static 클래스가 이와 같은 것을 알게 되었습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
java.lang.RuntimeException: Method d in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.
 
    at android.util.Log.d(Log.java)
    at hbs.com.androidstudy.ExampleUnitTest.안녕하세요(ExampleUnitTest.kt:21)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
cs


5. 우선, static을 피해서 각종 객체 접근하기

싱글톤으로 만들어야하는 객체들을 최대한 배제하고, Retrofit을 사용하는 쪽으로 방향을 잡았습니다.
PowerMockito나 robolectric와 같은 라이브러리 혹은 프레임워크를 사용해봤지만, 알아야하는 것이 어느 정도 있었고, JUnit4를 통해서도 static 키워드를 피해간다면, 결과값 정도는 나오는 것을 알 수 있었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ExampleUnitTest {
    private lateinit var jobCafeUseCase: JobCafeUseCase
    @Before
    fun setupTest() {
        jobCafeUseCase = JobCafeUseCaseImpl(
            JobCafeRepositoryImpl(
                RetrofitProvider.provideSeoulApi()
            )
        )
    }
 
    @Test
    fun getJobCafe() {
        jobCafeUseCase
            .getJobCafeList()
            .subscribe { wrappingJobCafeList ->
                println(wrappingJobCafeList.jobCafeList.jobCafes.toString())
            }
    }
}
cs


위와 같은 방식으로 retrofit의 객체를 만들어서 Test를 해본 결과 정상적으로 작동하는 것을 알 수 있었습니다.


추후 수정 예정/

  1. https://www.vogella.com/tutorials/AndroidTesting/article.html#androidtesting_projectstructure [본문으로]