Compose Assertion Vs. General test assertions

Giyun Kim·2026년 3월 9일

0. 의문점

UI 변경 사항에 대해 자동 동기화를 진행하는 Compose Test API Assertion만 쓰면 되지, 왜 일반적인 test assertions도 함께 활용하는 걸까?

  • 위 의문을 갖고 질의한 뒤, 레퍼런스를 읽어보며 정리해보았다.

1. 개념

1-1. 참고 문서

Android 앱 테스트의 기본사항  |  Test your app on Android  |  Android Developers

Semantics  |  Jetpack Compose  |  Android Developers

테스트 API  |  Jetpack Compose  |  Android Developers

하기 모든 내용은 위 레퍼런스에 기재되어 있다.

1-2. Compose Test API Assertion

Compose Test API에서 제공하는 assertion으로, Compose UI Semantics Tree를 대상으로 한다.

일반적인 로직 테스트만을 주로 다루던 우리에게는 상당히 생소한 UI 테스트 개념이다.

  • 특징
    • UI가 idle 상태가 될 때까지 자동으로 동기화한다.
    • Compose UI Semantics Tree 기반으로 노드를 찾고 검증한다.
    • 화면에 실제로 표현되는 UI 상태 검증에 사용한다.
  • 예시
    composeTestRule
        .onNodeWithText("카드")
        .assertExists()
    화면에 ‘카드’라는 텍스트가 보이는지 여부처럼, UI에 드러난 결과를 검증할 때는 UI 테스트이므로 Compose Test API를 사용하는 것이 적절하다.

1-3. 일반적인 test assertions

일반적인 kotlin.test assertion으로, 반드시 JUnit과 함께 쓰이는 것은 아니지만 우리가 늘 볼 Android/JVM 환경에서는 보통 JUnit 계열 구현과 함께 사용된다. test 어노테이션을 열어보았을 때, 아래의 예시를 볼 수 있다.

/**
 * @test annotation
 */
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@file:JvmPackageName("kotlin.test.junit.annotations")

package kotlin.test

public actual typealias Test = org.junit.Test
public actual typealias Ignore = org.junit.Ignore
public actual typealias BeforeTest = org.junit.Before
public actual typealias AfterTest = org.junit.After
  • 특징
    • 일반적인 Kotlin 값이나 비즈니스 로직을 검증할 때 사용한다.
    • Compose와 자동 동기화 되지 않으므로, 필요하면 WaitForIdle()등과 함께 사용한다.
  • 예시
    composeTestRule.waitForIdle()
    assertEquals(Success, viewModel.state.value)
    위의 예시는 필자가 임의로 작성한 ‘ViewModel의 상태 값이 Success인지 검증’하는 코드인데, UI 검증이 아니라 순수 값 검증이므로 General Test Assertion을 사용한다.

2. 테스트 설계 원칙

2-1. 핵심 원칙

  • 비즈니스 로직은 UI 밖에서 테스트해야 한다.
  • 작은 Unit Test를 많이 작성하고, 큰 UI 테스트나 통합 테스트는 최소한으로 유지한다.
  • UI 테스트는 실제 사용자 플로우, 화면 동작과 같은 필수 경로만 검증하도록 제한하는 것이 좋다.

따라서 그럼 그냥 Compose Test API만 쓰면 되지 않나? 라는 질문의 답은 아니다.

2-2. 이유

  1. UI에 드러나지 않는 비즈니스 로직 값은 Compose Test Assertion로 검증하기 어렵다.
    • Compose Test는 Semantics Tree를 통해 화면에 노출된 UI 요소만 다룬다.
    • Repository, UseCase, ViewModel 내부 상태처럼 UI에 직접 드러나지 않는 값은 General Test assertion으로 검증하는 것이 좋다.
  2. UI 테스트는 일반적인 Test보다 비용이 꽤 크다.
    • 실제 모바일 기기나 에뮬레이터, 앱 구동, Compose 렌더링 등의 공수가 포함되기 때문에, 순수 JVM에서 돌아가는 작은 unit 테스트보다 느리고 관리 비용이 클 수밖에 없다.
  3. 검증 가능하다고 해서 모두 UI 테스트로 검증하는 것은 권장되지 않는다.
    • 프레임워크 의존도가 큰 테스트는 유지보수성과 속도 측면에서 지양해야 한다.
    • 비즈니스 로직은 가능한 한 프레임워크에서 분리하고, UI는 그 로직이 화면에 잘 반영되는지만 Compose Assertion으로 확인하도록 분리하는 것이 좋다.

3. 정리

  • Compose Test의 assertion은 UI를 검증할 때, Kotlin test assertions은 state나 비즈니스 로직을 검증할 때 사용한다.
  • WaitForIdle()을 붙여가면서 쓰는 것은, Kotlin test assertion으로 Compose UI에서 파생된 값을 안전하게 읽고 싶을 때를 위한 보조 수단이라서 그런 것이지, Kotlin test assertion이 Compose assertion의 상위 호환이라서가 아니다.
  • 두 도구는 애초에 지향하는 검증 타겟이 다르다.
  • 단, 강의에서 봤던 계산기 예제처럼, 검증하려는 값이 Composition이나 Coroutine 작업 이후에 갱신되는 경우라면?
    • waitForIdle()은 Compose 테스트가 추적하는 UI 작업이 idle 상태가 될 때까지 기다리기 위한 도구로 볼 수 있다.
      • 다만 테스트가 인지하지 못하는 모든 백그라운드 비동기 작업까지 보장해 주는 것은 아니다!
    • waitForIdle()로 Compose 테스트가 추적하는 UI 작업이 idle 상태가 될 때까지 기다린 뒤, 일반적인 Kotlin test assertion으로 값을 검증할 수 있다.

4. 세 줄 요약

  1. Compose Test의 assert가 더 좋은 assert라서 General Test assertion을 대체하는 것이 아니라, 두 tool 간 검증 타겟이 다르다.
  2. UI를 검증하면 Compose Assertion을, 값이나 로직을 검증하면 General Test Assertion을 사용한다.
  3. 개념이 상당히 헷갈릴 수 있다.

5. 마치며

UI 테스트만을 고려했던 질의였는데, 결과적으로 크게 의미가 없었던 질문이었다. 다만, 이를 계기로 레퍼런스를 살펴보며 나름대로 학습을 할 수 있었으니 오히려 좋았다고 하자.

profile
Android 개발자가 되기까지

0개의 댓글