주완 님의 블로그
안드로이드 테스트 코드 작성 + Jetpack Compose UI 테스트 본문
Jetpack Compose에서 테스트 | Android Developers
Jetpack Compose에서 테스트 | Android Developers
이 Codelab에서는 Jetpack Compose로 만든 UI를 테스트하는 방법을 알아봅니다. 격리 테스트, 디버깅 테스트, 시맨틱 트리, 동기화를 알아보면서 첫 번째 테스트를 작성합니다.
developer.android.com
https://developer.android.com/develop/ui/compose/testing?hl=ko
Compose 레이아웃 테스트 | Jetpack Compose | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. Compose 레이아웃 테스트 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 앱의 UI를 테스트하여 Compose 코
developer.android.com
안드로이드 테스트 코드 작성 + Jetpack Compose UI 테스트
왜 테스트 코드를 작성해야 할까?
앱을 개발하다 보면 다음과 같은 상황을 맞이하게 됩니다:
- 기능이 많아질수록 리팩토링이 불안해짐
- 버그가 발생했을 때 어디서 터졌는지 확신이 없음
- 새롭게 합류한 팀원이 기존 기능을 수정할 때 믿고 맡기기 어려움
이런 문제를 해결하는 가장 강력한 도구가 바로 테스트 코드입니다.
테스트 코드란?
테스트 코드는 내가 작성한 기능이 의도대로 동작하는지를 자동으로 검증해주는 코드입니다.
- 기능의 안정성 보장
- 리팩토링 시 회귀 방지
- 모듈화된 아키텍처 테스트 용이성 ↑
특히 우리가 사용하는 MVVM, 클린 아키텍처 등의 설계는 테스트 용이성을 극대화하기 위해 등장한 패턴이기도 합니다.
테스트 환경 설정 (Gradle 의존성)
kotlin
복사편집
android {
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
testOptions {
unitTests.isIncludeAndroidResources = true
}
}
dependencies {
// JUnit
testImplementation("junit:junit:4.13.2")
// Kotlin Coroutines 테스트
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1")
// Mockito
testImplementation("org.mockito:mockito-core:5.2.0")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0")
// Robolectric (Android 리소스 테스트)
testImplementation("org.robolectric:robolectric:4.11.1")
// AndroidX Test
testImplementation("androidx.test:core:1.5.0")
}
- src/test/java → JVM 환경 테스트 (빠름)
- src/androidTest/java → 실제 Android 환경 Instrumentation 테스트 (느림)
단위 테스트 (Unit Test)
단위 테스트란?
가장 작은 단위인 함수, 클래스, 메서드 단위로 테스트하는 방식입니다. 예:
kotlin
복사편집
class Calculator {
fun add(a: Int, b: Int): Int = a + b
}
class CalculatorTest {
@Test
fun `두 수를 더하면 결과가 정확해야 한다`() {
val calculator = Calculator()
assertEquals(5, calculator.add(2, 3))
}
}
단위 테스트는 빠르게 실행되며 Android 의존성 없이도 테스트가 가능합니다.
테스트의 핵심 개념: Mock vs Fake
항목 Mock Fake
| 생성 방식 | Mockito 등 도구 사용 | 직접 구현 |
| 목적 | 동작 시나리오 정의 | 실제 동작 흉내 |
| 예시 | mock<UserRepository>() | class FakeUserRepository : UserRepository |
| 장점 | 유연한 조작 | 현실적인 테스트 |
| 단점 | 구조 파악 필요 | 구현 부담 |
아키텍처가 테스트에 유리한 이유
클린 아키텍처나 MVVM은 다음과 같은 구조를 만들어 줍니다:
- ViewModel ↔ UseCase ↔ Repository
- Repository는 인터페이스로 추상화됨
- 외부 의존성은 제거되고 DI로 주입
덕분에 순수 Kotlin 환경에서 테스트가 가능하며, 다음과 같은 테스트가 자연스럽게 가능합니다:
kotlin
복사편집
@Test
fun `ViewModel이 사용자 이름을 잘 불러오는가`() {
val vm = UserViewModel(GetUserUseCase(FakeUserRepository()))
vm.loadUser()
assertEquals("Test", vm.name.value)
}
Android 의존성이 있는 경우?
- 인터페이스로 분리 + Fake 사용
- Robolectric 사용 (JVM에서 Android 동작 에뮬레이션)
kotlin
복사편집
@RunWith(RobolectricTestRunner::class)
class PreferencesTest {
@Test
fun `SharedPreferences 저장 후 불러오기`() {
val context = ApplicationProvider.getApplicationContext<Context>()
val prefs = PreferencesImpl(context)
prefs.save("name", "Sangwook")
assertEquals("Sangwook", prefs.load("name"))
}
}
Jetpack Compose UI 테스트 완전 정복
왜 Compose UI 테스트를 배워야 할까?
Compose는 선언형 UI이기 때문에 기존 View 기반의 테스트 방식과 다릅니다. 특히 다음과 같은 특징이 있습니다:
- UI는 상태에 따라 변화
- 로직은 대부분 ViewModel로 분리
- 사용자 상호작용이 UI 상태에 직결
그렇기 때문에 UI 테스트는 단순 렌더링 확인을 넘어서 상태와 상호작용을 함께 검증해야 합니다.
Compose UI 테스트 환경 설정
kotlin
복사편집
androidTestImplementation "androidx.compose.ui:ui-test-junit4:<version>"
debugImplementation "androidx.compose.ui:ui-test-manifest:<version>"
기본 구조
kotlin
복사편집
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun myTest() {
composeTestRule.setContent {
MyComposable()
}
composeTestRule.onNodeWithText("Hello").assertIsDisplayed()
}
- setContent {}: 테스트할 Composable 정의
- onNodeWithText(), onNodeWithTag() 등으로 UI 탐색
- assertIsDisplayed(), performClick() 등으로 검증
UI 요소 탐색 방법
메서드 설명
| onNodeWithText("텍스트") | 텍스트로 탐색 |
| onNodeWithTag("tag") | Modifier.testTag() 기반 탐색 |
| onAllNodesWithText() | 동일 텍스트 여러 개 탐색 |
| onNode(hasClickAction()) | 클릭 가능한 요소 찾기 |
kotlin
복사편집
Button(
onClick = {},
modifier = Modifier.testTag("loginBtn")
) {
Text("Login")
}
kotlin
복사편집
composeTestRule.onNodeWithTag("loginBtn").performClick()
사용자 상호작용 테스트
클릭
kotlin
복사편집
composeTestRule
.onNodeWithText("Submit")
.performClick()
텍스트 입력
kotlin
복사편집
composeTestRule
.onNodeWithTag("inputField")
.performTextInput("Hello Compose")
스크롤
kotlin
복사편집
composeTestRule
.onNodeWithTag("list")
.performScrollToNode(hasText("Item 50"))
상태 변화 테스트
kotlin
복사편집
@Test
fun buttonClick_changesText() {
composeTestRule.setContent {
var text by remember { mutableStateOf("Before") }
Column {
Text(text)
Button(onClick = { text = "After" }) {
Text("Click")
}
}
}
composeTestRule.onNodeWithText("Click").performClick()
composeTestRule.onNodeWithText("After").assertIsDisplayed()
}
- 테스트 함수는 행동 + 기대 결과로 이름 짓기
- 예: clickingLoginButton_navigatesToHomeScreen()
ViewModel과 UI의 통합 테스트
kotlin
복사편집
@Test
fun viewModelState_showsCorrectText() {
val viewModel = MyViewModel().apply {
updateText("Loaded")
}
composeTestRule.setContent {
MyScreen(viewModel)
}
composeTestRule.onNodeWithText("Loaded").assertIsDisplayed()
}
UI의 변화는 대부분 ViewModel의 상태 변화에 기반하므로, ViewModel과 UI가 잘 연결됐는지 반드시 확인해야 합니다.
자주 사용하는 메서드 요약
메서드 설명
| assertIsDisplayed() | UI에 보이는지 |
| assertTextEquals() | 텍스트 정확히 일치하는지 |
| assertHasClickAction() | 클릭 가능한지 |
| performClick() | 클릭 |
| performTextInput("text") | 텍스트 입력 |
마무리 - 테스트는 선택이 아닌 필수
Jetpack Compose의 등장으로 UI 개발은 훨씬 생산적이고 직관적으로 바뀌었습니다. 그러나 이 구조에 맞는 테스트 전략을 모르면 실무에서 큰 장애물이 될 수 있습니다. 앱잼이 다가오는데 앱잼 전에 많이 공부하고 가야겠습니;다...
'Android' 카테고리의 다른 글
| 회원가입 플로우 아키텍처 분석: 다중 vs 단일 ViewModel , Navigation구조 (3) | 2025.08.27 |
|---|---|
| 구조 분해 선언과 component 함수 (kotlin in action) (1) | 2025.06.11 |
| DI - koin & Hilt (0) | 2025.05.25 |
| MVVM , repository 패턴 (1) | 2025.05.11 |
| Kotlin Study (kotlin in action) (0) | 2025.04.29 |