질문 정리

서버로 요청을 보내는 테스트 코드

five2week 2024. 4. 18. 10:56

API 테스트 코드를 작성하게 된 주요 이유

  1. 개발 도중 기획 변경으로 인해 API가 자주 수정되고 있습니다.
  2. API의 변경에 따라 안드로이드 코드도 연쇄적으로 수정해야 합니다.
  3. 서버의 소소한 변경사항에 대한 정보가 누락될 때가 있습니다.
  4. 서버가 재배포될 때마다 API 변경으로 인해 발생할 수 있는 오류를 확인하고자 합니다.

주요 JUnit 어노테이션

  • @Test: 이 어노테이션이 붙은 메소드는 JUnit에 의해 테스트로 인식되고 실행됩니다.
  • @Before: 각 테스트 메소드가 실행되기 전에 실행되어야 할 코드를 포함하는 메소드에 이 어노테이션을 사용합니다. 예를 들어, 테스트에 필요한 객체를 초기화하거나 테스트 환경을 설정하는 데 사용됩니다.
  • @After: 각 테스트 메소드의 실행이 끝난 후에 실행되어야 할 코드를 담고 있습니다. 주로 리소스 해제, 테스트 후 정리 작업 등을 수행합니다.
  • @BeforeClass: 해당 클래스의 모든 테스트가 실행되기 전에 단 한 번만 실행되어야 할 코드를 담습니다. static 메소드로 선언되어야 합니다.
  • @AfterClass: 모든 테스트 메소드의 실행이 완료된 후에 단 한 번 실행되어야 하는 코드를 포함합니다. 이 역시 static 메소드로 선언되어야 합니다.
  • @Ignore: 테스트를 일시적으로 비활성화할 때 사용합니다. 이 어노테이션이 붙은 테스트 메소드는 실행되지 않습니다.
class ApiServiceTest {
    companion object {
        lateinit var apiService: ApiService
        private lateinit var accessToken: String

        @BeforeClass
        @JvmStatic
        fun setUpClass() {
            val loggingInterceptor = HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BODY
            }

            val httpClient = OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor)
                .addInterceptor { chain ->
                    val originalRequest = chain.request()
                    val builder = originalRequest.newBuilder()

                    // Check if accessToken is initialized and not empty
                    if (this::accessToken.isInitialized && accessToken.isNotEmpty()) {
                        builder.addHeader("Authorization", "Bearer $accessToken")
                    }

                    chain.proceed(builder.build())
                }
                .build()

            apiService = Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(httpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
                .create(ApiService::class.java)

            val signInResponse = apiService.signIn("five2week@five2week.com", "five2week1234").execute()

            assertTrue(signInResponse.isSuccessful)
            assertNotNull("Response body should not be null", signInResponse.body())
            accessToken = signInResponse.body()?.accessToken ?: "
        }
    }

    @Test
    fun `test findUserId with valid data returns userId`() {
        val findIdRequest = FindIdRequest(
            username = "five2week",
            birthdate = "20240418",
            phone = "01012345678"
        )

        val response = apiService.findUserId(findIdRequest).execute()

        assertTrue("Response should be successful", response.isSuccessful)
        assertNotNull("Response body should not be null", response.body())
        assertEquals("Expected user ID", "five2week@five2week.com", response.body())
    }
    
      ...  
  }

해당 테스트를 통한 이점

  1. 자동화된 검증: API가 변경될 때마다 수동으로 테스트하는 것은 시간 소모적이고 에러를 발견하기 어려울 수 있습니다. 자동화된 테스트를 구현함으로써, 새롭게 배포된 API에 대한 신속하고 일관된 검증이 가능합니다.
  2. 버그 식별: 서버 측 변경사항이 안드로이드 애플리케이션에 영향을 주는 경우, 테스트 코드는 새로운 버그나 이슈를 초기 단계에서 발견하는 데 도움을 줄 수 있습니다.
  3. 소통의 부재 극복: 서버 개발자와의 소통이 원활하지 않을 때, 테스트 코드는 API의 스펙이 기대한 대로 동작하는지 확인하는 데 도움을 줍니다. 또한, 변경사항이 있을 때 테스트 실패를 통해 즉각적인 피드백을 받을 수 있습니다.
  4. 변경 관리: 서버의 재배포가 자주 일어나는 경우, 각 배포에서 API의 변화를 추적하고 관리하는 것이 중요합니다. 테스트 코드는 이러한 변경사항을 문서화하고 검증하는 효과적인 방법을 제공합니다.

앞으로 보완할 사항

  1. 요청을 하면 비용이 발생하는 Call은 테스트하고 있지 않습니다. 
  2. 현재 성공하는 경우의 수에 대해서만 테스트하고 있습니다. 실패하는 경우에도 적절히 대응하는지 테스트를 추가해야합니다.