혹시 API호출을 하는 기능을 테스트 할때
Mockito를 사용해서 Mock객체로 테스트해보셨나요?
Mock객체로 하면 굉장히 쉽고 빠르게 테스트가 가능하지만
실제 객체와 행동이 다른 경우가 발생 할 수 있어서
TestCoverage가 낮아집니다.
만약 테스트에서 실제 객체 사용이 가능하다면 Mock객체 사용을
지양하고 TestCoverage를 최대한 끌어올리는게 좋습니다.
실제 네트워크 연결하여 API를 호출하는 과정을 Coroutine을 이용해서
보여드리겠습니다.
일단 테스트할 API는 기상청 단기예보 API로 하겠습니다.
기상청 단기예보 API
회원가입을 하고 활용 신청후 APIkey를 얻어주세요!
출력 결과에 있는 값들을 data class로 만들어 줍시다!
여기서 꿀팁을 드리자면
출력결과를 전부 복사해서 ChatGPT에게 data class를 만들어 달라하면
손쉽게 data class를 만들 수 있습니다.
참쉽죠?
요청주소에 https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtNcst
이게 보이시나요?
baseUrl -> https://apis.data.go.kr
requestUrl -> /1360000/VilageFcstInfoService_2.0/getUltraSrtNcst
이 주소를 Retrofit을 만들때 사용하게 됩니다.
RequestUrl을 사용하는 모습이 보이죠?
마찬가지로 요청함수도 귀찮다면 GPT에게 맞겨줍시다.
만약 Retrofit이나 API 호출이 익숙하지 않다면 직접 타이핑하면서 연습해보세요!!
비동기 적으로 API를 호출 할 것이기 때문에 suspend fun으로 바꿔주고
Call을 Response로 바꿔주도록 합시다.
testImplementation ("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
해당 의존성을 app gradle에 추가해 줍니다.
< uses-permission android:name="android.permission.INTERNET"/>
테스트할 모듈의 Manifest에 권한을 추가해줍시다.
class ApiTest {
//1. 테스트 스코프 생성
private val job = Job()
private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher + job)
private lateinit var weatherService: WeatherService
//테스트 시작전에 실행되는 함수
@Before
fun setUp() {
//2. StandardTestDispatcher를 테스트에서 사용할 기본 Dispatcher로 지정!
Dispatchers.setMain(dispatcher)
//3. OkHttp 생성
val builder = OkHttpClient.Builder()
builder.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
//4. retrofit 생성
val retrofit = Retrofit.Builder()
.client(builder.build())
.baseUrl("https://apis.data.go.kr") // BaseUrl 설정
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
.build()
//5. Api호출을 위한 객체 생성
weatherService = retrofit.create(WeatherService::class.java)
}
//6. 테스트가 끝난후에 실행되는 함수
@After
fun tearDown() {
//Dispatcher 설정 해제
Dispatchers.resetMain()
}
}
1 ~ 6번까지 과정을 거쳐서 Test Class의 기본 설정을 마쳤습니다.
TestScope가 테스트동안 사용될 CoroutineScope입니다.
이제 설정을 마쳤으니 API 호출을 테스트 해보도록 합시다!
@Test
fun `날씨_데이터를_불러온다`() = testScope.runTest {
//given
val currentDate = LocalDate.now()
val oneHourAgo = LocalTime.now().minusMinutes(60)
val formattedTime = oneHourAgo.format(DateTimeFormatter.ofPattern("HHmm"))
val formattedDate = currentDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"))
//when
val response = weatherService.getWeather(
API_KEY,
1,
10,
"JSON",
formattedDate,
formattedTime,
59,
126
)
//then
assert(response.isSuccessful)
assert(response.body()!!.response.body.items.item.isNotEmpty())
}
Given - When - Then 형식에 따라 Test를 작성해봤습니다.
testScope.runTest를 통해
해당 함수가 TestScope안에서 동작하도록 해줍시다.
만약 테스트가 성공적이라면
response.isSuccessful 일 것이고
날짜 데이터를 제대로 가져왔을 것입니다.
테스트도 통과했고
interceptor를 통해 불러온 값도 정상 적인 것을 확인 할 수 있네요!
Mockito에 중독되면 실제 객체를 쓸 수 있어도
Mock 객체에 의존하게 되니 되도록이면 실제 객체를 사용하도록 합시다
참고로 포스트속 데이터 클래스 형식이 실제 소스코드와는 다릅니다.
예전에 만들고 방치한 프로젝트라 기억이 안나네요.
전체 소스 코드를 참고 부탁드립니다...
https://github.com/lyh990517/RealTime_Weather_App_with_jetpack_compose