안녕하세요. 🙂 Android를 담당하고 있는 다나, 밀리입니다! 🙇🏻♀️
공비서 CRM Android 앱을 좀 더 효율적으로 구현하기 위해 Mock API를 도입하여 이를 소개해보려고 합니다!
우선, Android 셀원들의 코드를 빠르게 이해하고 구현할 수 있도록 Mock 데이터의 JSON 파일을 생성하기 위한 컨벤션을 정의했습니다.
저장 공간으로 testFixtures 모듈 사용
테스트 및 기능 테스트에 필요한 Mock API 데이터 및 테스트 코드를 위한 Mock 데이터를 공통된 JSON 파일로 사용하기 위해 testFixtures 모듈을 활용하기로 했습니다.
<aside> 💡 testFixtures module이란?
JSON 파일명 규칙 정의
API request path를 기준으로 파일명의 컨벤션을 정의했습니다.
이를 통해 각 파일이 어떤 요청에 대응되는지 빠르게 파악할 수 있었습니다.😆
파일명 구분자는 _
Request method: GET/POST/PATCH/PUT 소문자
Request URL path
Request URL query 또는 param
Response code
Response apiResult
Account ID (임시: herren)
ex)
<aside>
💡 @GET("/api/v2/users/mock/{$TEST_NO}")
→ get_api_v2_users_2_200_success_herren.json
</aside>
API 도메인 별로 폴더 분리
모든 JSON 파일을 한 곳에 두면 관리가 어려워집니다. 따라서 API 도메인에 따라 폴더를 분리하여 구조화하고, 각 도메인에 해당하는 폴더 경로를 반환하는 함수를 구현했습니다.
이를 통해 관련 파일을 빠르게 찾을 수 있었습니다.😏
/**
* request url의 각 도메인에 해당하는 폴더 경로를 반환하는 함수
* */
private fun getDomainFolder(path: String): String {
with(path) {
return when {
contains("login") -> "login"
contains("users") -> "users"
// 다른 도메인에 대한 분기 처리 ...
else -> "shop"
}
}
}
보통 앱을 개발할 때에는 기획, 디자인, API(서버)가 필요합니다. 그러나 이러한 세 단계를 차례대로 진행하면 실제 API가 완성될 때까지 상당한 시간이 소요될 수 있습니다.😢
디자인 기반으로 UI를 먼저 구현하고 실제 API가 완성될 때까지 기다린 후 연결하는 식으로 작업을 진행했더니, 프로젝트 중간에 텀이 생겨 몰입도가 깨지거나, 프로젝트 끝 무렵에 급하게 작업하는 경우가 많이 있었습니다.
그렇다면, 이러한 시간 소요를 줄이기 위해 어떻게 하면 좋을까요?
기획 및 디자인 단계에서 백엔드 셀과의 협의를 통해 Mock API나 데이터를 미리 얻을 수 있습니다. 이를 활용하여 Android 앱의 화면과 기능을 실제 API 없이도 먼저 구현할 수 있습니다.
<aside> 💡 백엔드 셀에서 실제 API를 개발하는 동안 Android 셀은 이미 디자인과 Mock API를 활용하여 화면의 초기 버전을 만들어 놓을 수 있습니다. 나중에 실제 API가 완성되면 해당 API로 간단히 교체하여 최종 화면을 완성할 수 있게 됩니다👍
</aside>
JsonInterceptor
구현
네트워크 요청 및 응답 중에서 JSON 데이터를 가로채는 역할을 합니다.
class JsonInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val formattedFileName = StringBuilder().run {
// 정의된 컨벤션에 따라 파일명 생성 ...
append(".json")
}
println("fileName: ${getDomainFolder(uri.path.default())}/$formattedFileName")
val inputStream = javaClass.classLoader?.getResourceAsStream(
"${getDomainFolder(uri.path.default())}/$formattedFileName"
)
// 요청에 해당하는 .json 파일 읽기 / 해당하는 .json 파일이 없을 경우 실제 서버로 요청 전송
val source =
inputStream?.let { inputStream.source().buffer() } ?: return chain.proceed(request)
// 새로운 mock JSON response 생성
val response = chain.proceed(request)
.newBuilder()
.apply {
body(
source.readString(StandardCharsets.UTF_8)
.toResponseBody("application/json".toMediaType())
)
protocol(Protocol.HTTP_2)
addHeader("content-type", "application/json")
code(code.toInt())
}
.build()
return response
}
}
특정 상황에서만 동작하도록 설정
class JsonInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val isMock = request.url.queryParameter(MOCK)
...
// Mock api를 사용하고 싶지 않거나, Debug모드가 아니면 실제 서버로 요청 전송
if (isMock.not() || BuildConfig.DEBUG.not()) {
return chain.proceed(request)
}
...
}
}
OkHttpClient
에 JsonInterceptor
추가
공비서 CRM은 네트워크 통신을 하기 위해서 retrofit2+Okhttp 사용하므로, 구현한 JsonInterceptor
을 okHttpClientBuilder
에 추가해줍니다.
@Singleton
@Provides
fun provideOKHttpClient(
interceptor: Interceptor,
httpLoggingInterceptor: HttpLoggingInterceptor,
): OkHttpClient {
val okHttpClientBuilder =
OkHttpClient().newBuilder()
return okHttpClientBuilder
.addInterceptor(interceptor)
.addInterceptor(httpLoggingInterceptor)
.addInterceptor(JsonInterceptor()) // JsonInterceptor 추가
.build()
}
Service에서 query isMock: Boolean = true
로 호출
/**
* Mock api
*/
@GET("/api/v2/users/test")
suspend fun getMockTest(
@Header(GD_AUTH_TOKEN) token: String,
@Query(JsonInterceptor.MOCK) isMock: Boolean = true // ture: mock api / false: 실제 api)
): NetworkResponse<ResponseBase<MockTestEntity>, ErrorResponse>
/**
* 실제 api
*/
@GET("/api/v2/users/test")
suspend fun getMockTest(
@Header(GD_AUTH_TOKEN) token: String
): NetworkResponse<ResponseBase<MockTestEntity>, ErrorResponse>
개발 구현 속도 단축
Mock API를 활용함으로써 백엔드 셀에서 실제 API를 개발하는 동안에도 Android 셀에서는 이미 초기 버전의 화면을 개발할 수 있었습니다.
이로 인해 전체적인 개발 속도가 단축되었습니다. 👍
다양한 케이스 테스트 가능
Mock API를 활용하면 예외 케이스나 다양한 시나리오에 대한 테스트를 쉽게 수행할 수 있었습니다.
실제 API의 응답을 기다릴 필요 없이, 원하는 데이터를 빠르게 설정하여 다양한 시나리오를 테스트할 수 있었습니다. 👍👍
isMock 값으로 언제든지 전환 가능
JsonInterceptor에서 사용한 isMock 상수나 Debug 모드로 Mock API와 실제 API 간을 전환할 수 있어 편리했습니다.
이는 필요에 따라 실제 서버로 전환하여 실제 환경에서의 동작을 테스트할 수 있음을 의미합니다. 👍👍👍
테스트 코드 작성 용이
미리 정의된 JSON 파일을 사용하여 테스트 코드를 작성할 수 있었습니다.
특정 응답이 어떻게 처리되는지를 미리 알 수 있어 테스트 코드 작성이 편리해졌습니다. 👍👍👍👍
<aside> 💡 Mock API를 추출하기 위한 셀 간 협업을 강화하고, 개발 중 발생할 수 있는 다양한 상황에 대응할 수 있도록 도와주었습니다! 결론으로는 Android 앱 개발 프로세스를 효율적으로 만들어 주었습니다!!!
</aside>
지난 글에서 소개해 드렸던 것처럼 저희 Android셀에서는 API 응답 성공, 실패에 따른 비즈니스 로직을 검증하기 위해 ViewModel 에 대한 테스트 코드를 작성하고 있습니다.
저희 서비스에는 약 50개 정도의 ViewModel 이 있고 각 ViewModel 마다 적게는 1~2개 많게는 6~7개의 repository와 usecase를 주입해서 사용하고 있습니다.
각 ViewModel 에서 사용하는 모든 API 응답값 (성공 / 실패 / 예외 케이스)에 대해 Entity 데이터를 만들어서 작업하다보니 데이터 설정하는 작업도 하나의 일이 되어 귀찮고 불편하다는 의견이 나왔습니다. 😱
이런 불편함을 어떻게 해결할 수 있을까요?
Entity 또는 VO object 를 일일이 설정하지 않고 API 응답값으로 받는 Json 파일을 그대로 활용하면 데이터 설정 작업을 줄일 수 있습니다. 👍
MockWebServerService
클래스 구현