1. 시작하기 전에
이전 Codelab에서는 ViewModel
을 사용하여 비즈니스 로직을 처리하는 방법과 반응형 UI의 LiveData
를 알아봤습니다. 이 Codelab에서는 단위 테스트를 작성하여 ViewModel
코드가 제대로 작동하는지 확인하는 방법을 알아봅니다.
기본 요건
- Android 스튜디오에서 테스트 디렉터리를 만들어 보았습니다.
- Android 스튜디오에서 단위 테스트와 계측 테스트를 작성해 보았습니다.
- Android 프로젝트에 Gradle 종속 항목을 추가해 보았습니다.
학습할 내용
ViewModel
및LiveData
의 단위 테스트를 작성하는 방법
필요한 항목
- Android 스튜디오가 설치된 컴퓨터
- Cupcake 앱의 솔루션 코드
이 Codelab의 시작 코드 다운로드
이 Codelab에서는 이전 솔루션 코드의 Cupcake 앱에 계측 테스트를 추가합니다.
이 Codelab의 코드를 가져와서 Android 스튜디오에서 열려면 다음을 실행합니다.
코드 가져오기
- 제공된 URL을 클릭합니다. 브라우저에서 프로젝트의 GitHub 페이지가 열립니다.
- 브랜치 이름이 Codelab에 지정된 브랜치 이름과 일치하는지 검토하고 확인합니다. 예를 들어 다음 스크린샷에서 분기 이름은 main입니다.
- 프로젝트의 GitHub 페이지에서 Code 버튼을 클릭하여 팝업을 엽니다.
- 팝업에서 Download ZIP 버튼을 클릭하여 컴퓨터에 프로젝트를 저장합니다. 다운로드가 완료될 때까지 기다립니다.
- 컴퓨터에서 파일을 찾습니다(예: Downloads 폴더).
- ZIP 파일을 더블클릭하여 압축을 해제합니다. 프로젝트 파일이 포함된 새 폴더가 만들어집니다.
Android 스튜디오에서 프로젝트 열기
- Android 스튜디오를 시작합니다.
- Welcome to Android Studio 창에서 Open을 클릭합니다.
참고: Android 스튜디오가 이미 열려 있는 경우 File > Open 메뉴 옵션을 대신 선택합니다.
- 파일 브라우저에서 압축 해제된 프로젝트 폴더가 있는 위치로 이동합니다(예: Downloads 폴더).
- 프로젝트 폴더를 더블클릭합니다.
- Android 스튜디오가 프로젝트를 열 때까지 기다립니다.
- Run 버튼 을 클릭하여 앱을 빌드하고 실행합니다. 예상대로 작동하는지 확인합니다.
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
객체가 기본 스레드를 호출하면 안 된다고 명시적으로 지정해야 합니다.
LiveData
객체가 기본 스레드를 호출하면 안 된다고 지정하려면LiveData
객체를 테스트할 때마다 특정 테스트 규칙을 제공해야 합니다.
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
- 이제
quantity_twelve_cupcakes()
라는 함수를 만들 수 있습니다. 메서드에서OrderViewModel.
의 인스턴스를 만듭니다. - 이 테스트에서는
setQuantity
가 호출될 때OrderViewModel
의quantity
객체가 업데이트되었는지 확인합니다. 그러나 메서드를 호출하거나OrderViewModel
의 데이터로 작업하기 전에, 변경사항을 내보내려면LiveData
객체의 값을 테스트할 때 객체를 관찰해야 합니다. 이를 위한 간단한 방법은observeForever
메서드를 사용하는 것입니다.quantity
객체에서observeForever
메서드를 호출합니다. 이 메서드는 람다 표현식을 필요로 하지만 비워둘 수 있습니다. - 그런 다음
setQuantity()
메서드를 호출하고12
를 매개변수로 전달합니다.
val viewModel = OrderViewModel()
viewModel.quantity.observeForever {}
viewModel.setQuantity(12)
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
의 다른 메서드에서만 호출할 수 있습니다. 이러한 메서드는 공개 메서드이므로 가격 계산을 시작하기 위해 이러한 메서드를 호출하여 가격 값이 예상대로인지 확인할 수 있습니다.
권장사항
가격은 컵케이크 수량을 선택할 때와 날짜를 선택할 때 업데이트됩니다. 두 가지 모두 테스트해야 하지만 일반적으로 단일 기능만 테스트하는 것이 좋습니다. 따라서 각 테스트에 별도의 메서드를 만듭니다. 수량이 업데이트될 때 가격을 테스트하는 함수와 날짜가 업데이트될 때 가격을 테스트하는 별도의 함수입니다. 하나의 테스트가 실패하여 다른 테스트의 결과까지 실패하는 것을 바라지 않습니다.
price_twelve_cupcakes()
라는 메서드를 만들고 테스트로 주석을 답니다.- 메서드에서
OrderViewModel
인스턴스를 만들고setQuantity()
메서드를 호출하여 12를 매개변수로 전달합니다.
val viewModel = OrderViewModel()
viewModel.setQuantity(12)
OrderViewModel
의PRICE_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)
이제 테스트를 실행합니다.
실패합니다.
테스트 결과에 실제 값이 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
객체를 변환할 때 이 코드는 꼭 필요한 상황이 아니면 호출되지 않으므로 휴대기기의 리소스가 절약됩니다. 이 코드는 객체의 변경사항을 관찰하는 경우에만 호출됩니다. 물론 이 작업은 앱에서 실행되지만 테스트에서도 같은 작업을 해야 합니다.
- 수량을 설정하기 전에 테스트 메서드에서 다음 줄을 추가합니다.
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
를 관찰하는 방법을 알아봤습니다.