ViewModel 및 LiveData 테스트

1. 시작하기 전에

이전 Codelab에서는 ViewModel을 사용하여 비즈니스 로직을 처리하는 방법과 반응형 UI의 LiveData를 알아봤습니다. 이 Codelab에서는 단위 테스트를 작성하여 ViewModel 코드가 제대로 작동하는지 확인하는 방법을 알아봅니다.

기본 요건

  • Android 스튜디오에서 테스트 디렉터리를 만들어 보았습니다.
  • Android 스튜디오에서 단위 테스트와 계측 테스트를 작성해 보았습니다.
  • Android 프로젝트에 Gradle 종속 항목을 추가해 보았습니다.

학습할 내용

  • ViewModelLiveData의 단위 테스트를 작성하는 방법

필요한 항목

  • Android 스튜디오가 설치된 컴퓨터
  • Cupcake 앱의 솔루션 코드

이 Codelab의 시작 코드 다운로드

이 Codelab에서는 이전 솔루션 코드의 Cupcake 앱에 계측 테스트를 추가합니다.

이 Codelab의 코드를 가져와서 Android 스튜디오에서 열려면 다음을 실행합니다.

코드 가져오기

  1. 제공된 URL을 클릭합니다. 브라우저에서 프로젝트의 GitHub 페이지가 열립니다.
  2. 브랜치 이름이 Codelab에 지정된 브랜치 이름과 일치하는지 검토하고 확인합니다. 예를 들어 다음 스크린샷에서 분기 이름은 main입니다.

fe29aa9112862a93.png

  1. 프로젝트의 GitHub 페이지에서 Code 버튼을 클릭하여 팝업을 엽니다.

5b0a76c50478a73f.png

  1. 팝업에서 Download ZIP 버튼을 클릭하여 컴퓨터에 프로젝트를 저장합니다. 다운로드가 완료될 때까지 기다립니다.
  2. 컴퓨터에서 파일을 찾습니다(예: Downloads 폴더).
  3. ZIP 파일을 더블클릭하여 압축을 해제합니다. 프로젝트 파일이 포함된 새 폴더가 만들어집니다.

Android 스튜디오에서 프로젝트 열기

  1. Android 스튜디오를 시작합니다.
  2. Welcome to Android Studio 창에서 Open을 클릭합니다.

a065e3d575fe607b.png

참고: Android 스튜디오가 이미 열려 있는 경우 File > Open 메뉴 옵션을 대신 선택합니다.

4f3b1e628c7695f1.png

  1. 파일 브라우저에서 압축 해제된 프로젝트 폴더가 있는 위치로 이동합니다(예: Downloads 폴더).
  2. 프로젝트 폴더를 더블클릭합니다.
  3. Android 스튜디오가 프로젝트를 열 때까지 기다립니다.
  4. Run 버튼 11c34fc5e516fb1c.png을 클릭하여 앱을 빌드하고 실행합니다. 예상대로 작동하는지 확인합니다.

2. 시작 앱 개요

Cupcake 앱은 컵케이크 수량 옵션이 세 개인 주문 화면을 표시하는 홈 화면으로 구성됩니다. 옵션을 클릭하면 맛을 선택하는 화면으로 이동하고 주문 수령 날짜를 선택하는 화면으로 이동합니다. 그런 다음 다른 앱으로 주문을 보낼 수 있습니다. 이러한 단계 중 어디서든 주문을 취소할 수 있습니다.

3. 단위 테스트 디렉터리 만들기

이전 Codelab에서 했던 것처럼 Cupcake 앱의 단위 테스트 디렉터리를 만듭니다.

4. 단위 테스트 클래스 만들기

ViewModelTests.kt라는 새 클래스를 만듭니다.

5. 필요한 종속 항목 추가

다음 종속 항목을 프로젝트에 추가합니다.

testImplementation 'junit:junit:4.+'
testImplementation 'androidx.arch.core:core-testing:2.1.0'

이제 프로젝트를 동기화합니다.

6. ViewModel 테스트 작성

간단한 테스트부터 시작해 보겠습니다. 기기나 에뮬레이터에서 앱과 상호작용할 때 가장 먼저 하는 작업은 컵케이크 수량을 선택하는 것입니다. 따라서 먼저 OrderViewModel에서 setQuantity() 메서드를 테스트하고 quantity LiveData 객체의 값을 확인합니다.

테스트할 quantity 변수는 LiveData의 인스턴스입니다. LiveData 객체를 테스트하려면 추가 단계가 필요하며 여기서 추가한 종속 항목이 중요한 역할을 합니다. 값이 변경되는 즉시 LiveData를 사용하여 UI를 업데이트합니다. UI는 '기본 스레드'라고 하는 것에서 실행됩니다. 스레딩과 동시 실행을 잘 몰라도 괜찮습니다. 다른 Codelab에서 자세히 알아볼 예정입니다. 지금은 Android 앱이라는 컨텍스트에서 기본 스레드를 UI 스레드로 생각하세요. 사용자에게 UI를 표시하는 코드는 이 스레드에서 실행됩니다. 달리 명시되지 않는 한 단위 테스트는 모든 항목이 기본 스레드에서 실행된다고 가정합니다. 그러나 LiveData 객체는 기본 스레드에 액세스할 수 없으므로 LiveData 객체가 기본 스레드를 호출하면 안 된다고 명시적으로 지정해야 합니다.

  1. LiveData 객체가 기본 스레드를 호출하면 안 된다고 지정하려면 LiveData 객체를 테스트할 때마다 특정 테스트 규칙을 제공해야 합니다.
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
  1. 이제 quantity_twelve_cupcakes()라는 함수를 만들 수 있습니다. 메서드에서 OrderViewModel.의 인스턴스를 만듭니다.
  2. 이 테스트에서는 setQuantity가 호출될 때 OrderViewModelquantity 객체가 업데이트되었는지 확인합니다. 그러나 메서드를 호출하거나 OrderViewModel의 데이터로 작업하기 전에, 변경사항을 내보내려면 LiveData 객체의 값을 테스트할 때 객체를 관찰해야 합니다. 이를 위한 간단한 방법은 observeForever 메서드를 사용하는 것입니다. quantity 객체에서 observeForever 메서드를 호출합니다. 이 메서드는 람다 표현식을 필요로 하지만 비워둘 수 있습니다.
  3. 그런 다음 setQuantity() 메서드를 호출하고 12를 매개변수로 전달합니다.
val viewModel = OrderViewModel()
viewModel.quantity.observeForever {}
viewModel.setQuantity(12)
  1. quantity 객체의 값이 12라고 안전하게 추론할 수 있습니다. LiveData 객체는 값 자체가 아닙니다. 값은 value라는 속성에 포함되어 있습니다. 다음과 같이 어설션을 만듭니다.
assertEquals(12, viewModel.quantity.value)

테스트는 다음과 같이 표시됩니다.

@Test
fun quantity_twelve_cupcakes() {
   val viewModel = OrderViewModel()
   viewModel.quantity.observeForever {}
   viewModel.setQuantity(12)
   assertEquals(12, viewModel.quantity.value)
}

테스트를 실행합니다. 축하합니다. 방금 첫 번째 LiveData 단위 테스트를 작성했습니다. 이는 최신 Android 개발에서 중요한 기술입니다. 이 테스트는 많은 비즈니스 로직을 테스트하지 않으므로 좀 더 복잡한 테스트를 작성해 보겠습니다.

OrderViewModel의 주요 기능 중 하나는 주문 가격을 계산하는 것입니다. 이는 컵케이크 수량을 선택할 때와 수령 날짜를 선택할 때 발생합니다. 가격 계산은 비공개 메서드에서 실행되므로 테스트에서는 이 메서드를 직접 호출할 수 없습니다. OrderViewModel의 다른 메서드에서만 호출할 수 있습니다. 이러한 메서드는 공개 메서드이므로 가격 계산을 시작하기 위해 이러한 메서드를 호출하여 가격 값이 예상대로인지 확인할 수 있습니다.

권장사항

가격은 컵케이크 수량을 선택할 때와 날짜를 선택할 때 업데이트됩니다. 두 가지 모두 테스트해야 하지만 일반적으로 단일 기능만 테스트하는 것이 좋습니다. 따라서 각 테스트에 별도의 메서드를 만듭니다. 수량이 업데이트될 때 가격을 테스트하는 함수와 날짜가 업데이트될 때 가격을 테스트하는 별도의 함수입니다. 하나의 테스트가 실패하여 다른 테스트의 결과까지 실패하는 것을 바라지 않습니다.

  1. price_twelve_cupcakes()라는 메서드를 만들고 테스트로 주석을 답니다.
  2. 메서드에서 OrderViewModel 인스턴스를 만들고 setQuantity() 메서드를 호출하여 12를 매개변수로 전달합니다.
val viewModel = OrderViewModel()
viewModel.setQuantity(12)
  1. OrderViewModelPRICE_PER_CUPCAKE를 보면 컵케이크가 개당 $2.00임을 알 수 있습니다. ViewModel이 초기화될 때마다 resetOrder()가 호출되며 이 메서드에서 기본 날짜는 오늘 날짜이고 PRICE_FOR_SAME_DAY_PICKUP은 $3.00인 것도 확인할 수 있습니다. 따라서 12 * 2 + 3 = 27입니다. 컵케이크를 12개 선택한 후 price 변수의 값은 $27.00가 될 것으로 예상됩니다. 따라서 예상값 $27.00가 price LiveData 객체의 값과 같다고 어설션을 만들어 보겠습니다.
assertEquals("$27.00", viewModel.price.value)

이제 테스트를 실행합니다.

실패합니다.

17c8a24e4d7d635d.png

테스트 결과에 실제 값이 null이라고 표시됩니다. 이에 관해 설명해 보겠습니다. OrderViewModel에서 price 변수를 살펴보면 다음을 확인할 수 있습니다.

val price: LiveData<String> = Transformations.map(_price) {
   // Format the price into the local currency and return this as LiveData<String>
   NumberFormat.getCurrencyInstance().format(it)
}

다음은 테스트에서 LiveData를 관찰해야 하는 이유를 설명하는 예입니다. price의 값은 Transformation을 사용하여 설정됩니다. 기본적으로 이 코드는 price에 할당하는 값을 가져와서 통화 형식으로 변환하므로 수동으로 변환하지 않아도 됩니다. 그러나 이 코드에는 다른 의미가 있습니다. LiveData 객체를 변환할 때 이 코드는 꼭 필요한 상황이 아니면 호출되지 않으므로 휴대기기의 리소스가 절약됩니다. 이 코드는 객체의 변경사항을 관찰하는 경우에만 호출됩니다. 물론 이 작업은 앱에서 실행되지만 테스트에서도 같은 작업을 해야 합니다.

  1. 수량을 설정하기 전에 테스트 메서드에서 다음 줄을 추가합니다.
viewModel.price.observeForever {}

테스트는 다음과 같이 표시됩니다.

@Test
fun price_twelve_cupcakes() {
   val viewModel = OrderViewModel()
   viewModel.price.observeForever {}
   viewModel.setQuantity(12)
   assertEquals("$27.00", viewModel.price.value)
}

이제 테스트를 실행하면 통과됩니다.

7. 솔루션 코드

8. 축하합니다

이 Codelab에서 배운 내용은 다음과 같습니다.

  • LiveData 테스트를 설정하는 방법을 알아봤습니다.
  • LiveData 자체를 테스트하는 방법을 알아봤습니다.
  • 변환된 LiveData를 테스트하는 방법을 알아봤습니다.
  • 단위 테스트에서 LiveData를 관찰하는 방법을 알아봤습니다.