Notice
Recent Posts
Recent Comments
Link
Tags
more
관리 메뉴

주완 님의 블로그

안드로이드 테스트 코드 작성 + Jetpack Compose UI 테스트 본문

Android

안드로이드 테스트 코드 작성 + Jetpack Compose UI 테스트

vvan2 2025. 6. 8. 21:50

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 의존성이 있는 경우?

  1. 인터페이스로 분리 + Fake 사용
  2. 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 개발은 훨씬 생산적이고 직관적으로 바뀌었습니다. 그러나 이 구조에 맞는 테스트 전략을 모르면 실무에서 큰 장애물이 될 수 있습니다. 앱잼이 다가오는데 앱잼 전에 많이 공부하고 가야겠습니;다...