Android에서 테스트 더블 사용

요소 또는 시스템의 테스트 전략을 설계할 때 다음과 같은 세 가지 관련 테스트 측면이 있습니다.

  • 범위: 테스트가 코드의 어느 정도까지 터치하나요? 테스트로 단일 메서드, 전체 애플리케이션 또는 그 사이의 지점을 검증할 수 있습니다. 테스트된 범위는 테스트 중이며 일반적으로 테스트 대상이라고 하지만 테스트 대상 시스템 또는 테스트 대상 단위라고 합니다.
  • 속도: 테스트가 얼마나 빠르게 실행되나요? 테스트 속도는 밀리초에서 몇 분까지 다양할 수 있습니다.
  • 충실도: 테스트가 얼마나 '실제' 환경인가요? 예를 들어 테스트 중인 코드의 일부가 네트워크 요청을 해야 한다면 테스트 코드가 실제로 이 네트워크 요청을 하는가, 아니면 결과를 가짜로 만드는 것일까요? 테스트가 실제로 네트워크와 통신한다면 충실도가 더 높다는 의미입니다. 단, 테스트 실행 시간이 더 오래 걸리거나 네트워크가 다운되면 오류가 발생하거나 사용 비용이 많이 들 수 있다는 단점이 있습니다.

테스트 전략 정의를 시작하는 방법을 알아보려면 테스트 항목을 참조하세요.

격리 및 종속 항목

요소 또는 요소 시스템을 테스트하는 경우 격리로 테스트하게 됩니다. 예를 들어 ViewModel을 테스트할 때 에뮬레이터를 시작하고 UI를 실행할 필요가 없습니다. Android 프레임워크에 종속되지 않거나 종속되면 안 되기 때문입니다.

그러나 테스트 대상이 작동하려면 다른 주체에 의존할 수 있습니다. 예를 들어 ViewModel은 데이터 저장소에 종속되어 작동할 수 있습니다.

테스트 대상에게 종속 항목을 제공해야 할 때 일반적인 방법은 테스트 더블 (또는 테스트 객체)을 만드는 것입니다. 테스트 더블은 앱에서 구성요소로 보이고 작동하는 객체이지만 특정 동작이나 데이터를 제공하기 위해 테스트에서 생성됩니다. 주요 장점은 테스트를 더 빠르고 간단하게 할 수 있다는 것입니다.

테스트 더블 유형

테스트 더블에는 다음과 같은 다양한 유형이 있습니다.

가짜 클래스의 '작동하는' 구현을 가지고 있지만 테스트에는 좋지만 프로덕션에는 적합하지 않은 방식으로 구현되는 테스트 더블입니다.

예: 메모리 내 데이터베이스

가짜는 모의 프레임워크가 필요 없고 가볍습니다. 권장됩니다.

예시 동작하도록 프로그래밍하는 방식으로 동작하고 상호작용에 관한 기대치를 갖는 테스트 더블입니다. 모의 스크린샷은 상호작용이 정의된 요구사항과 일치하지 않으면 테스트에 실패합니다. 모의 스크린샷은 보통 이러한 모든 목적을 달성하기 위해 모의 프레임워크로 만듭니다.

예: 데이터베이스의 메서드가 정확히 한 번 호출되었는지 확인합니다.

스텁 동작하도록 프로그래밍하는 방식으로 동작하지만 상호작용에 관한 기대치는 없는 테스트 더블입니다. 일반적으로 모의 프레임워크로 만들어집니다. 편의를 위해 스텁보다 가짜가 선호됩니다.
가짜 매개변수로 제공되어야 하는 경우와 같이 전달되었지만 사용되지 않는 테스트 더블입니다.

예: 클릭 콜백으로 전달된 빈 함수

스파이 모의 객체와 유사하게 일부 추가 정보도 추적하는 실제 객체의 래퍼입니다. 일반적으로 복잡성을 추가하기 위해 피합니다. 따라서 스파이보다 가짜 또는 모의 버전이 선호됩니다.
그림자 Robolectric에서 사용되는 가짜.

가짜 제품 사용 예

UserRepository라는 인터페이스에 종속되고 첫 번째 사용자의 이름을 UI에 노출하는 ViewModel을 단위 테스트하려고 한다고 가정해 보겠습니다. 인터페이스를 구현하고 알려진 데이터를 반환하여 모조 테스트 더블을 만들 수 있습니다.

object FakeUserRepository : UserRepository {
    fun getUsers() = listOf(UserAlice, UserBob)
}

val const UserAlice = User("Alice")
val const UserBob = User("Bob")

이러한 모조 UserRepository는 프로덕션 버전에서 사용할 로컬 및 원격 데이터 소스에 종속되지 않아도 됩니다. 파일은 테스트 소스 세트에 상주하며 프로덕션 앱과 함께 제공되지 않습니다.

모조 종속 항목은 원격 데이터 소스에 의존하지 않고 알려진 데이터를 반환할 수 있습니다.
그림 1: 단위 테스트의 모조 종속 항목

다음 테스트는 ViewModel이 첫 번째 사용자 이름을 뷰에 올바르게 노출하는지 확인합니다.

@Test
fun viewModelA_loadsUsers_showsFirstUser() {
    // Given a VM using fake data
    val viewModel = ViewModelA(FakeUserRepository) // Kicks off data load on init

    // Verify that the exposed data is correct
    assertEquals(viewModel.firstUserName, UserAlice.name)
}

ViewModel은 테스터에서 생성하므로 단위 테스트에서 UserRepository를 모조로 쉽게 바꿀 수 있습니다. 그러나 더 큰 테스트에서는 임의의 요소를 대체하기가 어려울 수 있습니다.

구성요소 교체 및 종속 항목 삽입

테스트에서 테스트 중인 시스템 생성을 제어할 수 없는 경우 테스트 더블의 구성요소를 교체하는 것이 더 까다로워지며 앱 아키텍처가 테스트 가능한 설계를 따라야 합니다.

앱의 전체 사용자 흐름을 탐색하는 계측 UI 테스트와 같은 테스트 더블을 사용하면 규모가 큰 엔드 투 엔드 테스트에서도 도움이 될 수 있습니다. 이 경우에는 테스트를 밀폐하는 것이 좋습니다. 밀폐 테스트는 인터넷에서 데이터 가져오기와 같은 모든 외부 종속 항목을 방지합니다. 이렇게 하면 안정성과 성능이 향상됩니다.

그림 2: 대부분의 앱을 대상으로 원격 데이터를 가짜로 테스트하는 대규모 테스트

이러한 유연성을 수동으로 달성하도록 앱을 설계할 수 있지만 Hilt와 같은 종속 항목 삽입 프레임워크를 사용하여 테스트 시 앱의 구성요소를 교체하는 것이 좋습니다. Hilt 테스트 가이드를 참고하세요.

Robolectric

Android에서는 특별한 유형의 테스트 더블을 제공하는 Robolectric 프레임워크를 사용할 수 있습니다. Robolectric을 사용하면 워크스테이션 또는 지속적 통합 환경에서 테스트를 실행할 수 있습니다. 에뮬레이터나 기기 없이 일반 JVM을 사용합니다. 섀도라는 테스트 더블을 사용하여 뷰, 리소스 로드 및 Android 프레임워크의 기타 부분의 확장을 시뮬레이션합니다.

Robolectric은 시뮬레이터이므로 간단한 단위 테스트를 대체하거나 호환성 테스트에 사용되어서는 안 됩니다. 경우에 따라 낮은 충실도를 희생하는 대신 속도를 제공하고 비용을 절감할 수 있습니다. UI 테스트의 좋은 접근 방식은 Robolectric 및 계측 테스트와 모두 호환되도록 하고 기능이나 호환성을 테스트할 필요성에 따라 실행할 시기를 결정하는 것입니다. Espresso 및 Compose 테스트는 모두 Robolectric에서 실행될 수 있습니다.