테스트 기본사항

이 Codelab은 Kotlin 기반 Android 고급 교육 과정의 일부입니다. Codelab을 순서대로 진행하는 경우 학습 효과를 극대화할 수 있지만 순서를 바꿔 진행해도 괜찮습니다. 모든 교육 과정 Codelab은 Kotlin 기반 고급 Android Codelab 방문 페이지에 나열되어 있습니다.

소개

첫 번째 앱의 첫 번째 기능을 구현할 때 코드를 실행하여 예상대로 작동하는지 확인했을 것입니다. 수동 테스트이긴 하지만 테스트를 실행했습니다. 기능을 계속 추가하고 업데이트하면서 코드를 계속 실행하고 작동하는지 확인했을 것입니다. 하지만 매번 수동으로 이 작업을 수행하는 것은 피로하고 오류가 발생하기 쉬우며 확장할 수 없습니다.

컴퓨터는 확장과 자동화에 매우 유용합니다. 따라서 크고 작은 회사의 개발자는 소프트웨어에서 실행되며 코드가 작동하는지 확인하기 위해 앱을 수동으로 작동할 필요가 없는 테스트인 자동 테스트를 작성합니다.

이 Codelab 시리즈에서는 실제 앱의 테스트 모음 (테스트 스위트라고 함)을 만드는 방법을 알아봅니다.

이 첫 번째 Codelab에서는 Android 테스트의 기본사항을 다루며, 첫 번째 테스트를 작성하고 LiveDataViewModel를 테스트하는 방법을 알아봅니다.

기본 요건

다음을 잘 알고 있어야 합니다.

학습할 내용

다음 주제에 대해 알아봅니다.

  • Android에서 단위 테스트를 작성하고 실행하는 방법
  • 테스트 주도 개발 사용 방법
  • 계측 테스트와 로컬 테스트를 선택하는 방법

다음 라이브러리 및 코드 개념을 알아봅니다.

실습할 내용

  • Android에서 로컬 테스트와 계측 테스트를 모두 설정, 실행, 해석합니다.
  • JUnit4 및 Hamcrest를 사용하여 Android에서 단위 테스트를 작성합니다.
  • 간단한 LiveDataViewModel 테스트를 작성합니다.

이 Codelab 시리즈에서는 TO-DO Notes 앱을 사용합니다. 이 앱을 사용하면 완료할 작업을 적어 목록에 표시할 수 있습니다. 그런 다음 완료됨 또는 미완료로 표시하거나, 필터링하거나, 삭제할 수 있습니다.

이 앱은 Kotlin으로 작성되었으며, 여러 화면이 있고, Jetpack 구성요소를 사용하며, 앱 아키텍처 가이드의 아키텍처를 따릅니다. 이 앱을 테스트하는 방법을 배우면 동일한 라이브러리와 아키텍처를 사용하는 앱을 테스트할 수 있습니다.

시작하려면 코드를 다운로드합니다.

ZIP 파일 다운로드

또는 코드에 관한 GitHub 저장소를 클론해도 됩니다.

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout starter_code

이 작업에서는 앱을 실행하고 코드베이스를 살펴봅니다.

1단계: 샘플 앱 실행

할 일 앱을 다운로드한 후 Android 스튜디오에서 열고 실행합니다. 컴파일되어야 합니다. 다음을 실행하여 앱을 탐색합니다.

  • 플러스 플로팅 액션 버튼으로 새 작업을 만듭니다. 먼저 제목을 입력한 다음 할 일에 관한 추가 정보를 입력합니다. 녹색 체크 FAB로 저장합니다.
  • 작업 목록에서 방금 완료한 작업의 제목을 클릭하고 해당 작업의 세부정보 화면을 확인하여 나머지 설명을 확인합니다.
  • 목록 또는 세부정보 화면에서 해당 작업의 체크박스를 선택하여 상태를 완료됨으로 설정합니다.
  • 작업 화면으로 돌아가 필터 메뉴를 열고 활성완료됨 상태별로 작업을 필터링합니다.
  • 탐색 창을 열고 통계를 클릭합니다.
  • 개요 화면으로 돌아가 탐색 메뉴에서 완료된 항목 삭제를 선택하여 상태가 완료됨인 모든 작업을 삭제합니다.

2단계: 샘플 앱 코드 살펴보기

할 일 앱은 인기 있는 Architecture Blueprints 테스트 및 아키텍처 샘플 (샘플의 반응형 아키텍처 버전 사용)을 기반으로 합니다. 앱은 앱 아키텍처 가이드의 아키텍처를 따릅니다. 프래그먼트, 저장소, Room과 함께 ViewModel을 사용합니다. 아래 예 중 하나에 익숙하다면 이 앱의 아키텍처가 유사합니다.

특정 레이어의 로직을 깊이 이해하는 것보다 앱의 일반적인 아키텍처를 이해하는 것이 더 중요합니다.

다음은 확인할 수 있는 패키지 요약입니다.

패키지: com.example.android.architecture.blueprints.todoapp

.addedittask

할 일 추가 또는 수정 화면: 할 일을 추가하거나 수정하는 UI 레이어 코드입니다.

.data

데이터 영역: 작업의 데이터 영역을 처리합니다. 여기에는 데이터베이스, 네트워크, 저장소 코드가 포함됩니다.

.statistics

통계 화면: 통계 화면의 UI 레이어 코드입니다.

.taskdetail

작업 세부정보 화면: 단일 작업의 UI 레이어 코드입니다.

.tasks

작업 화면: 모든 작업 목록의 UI 레이어 코드입니다.

.util

유틸리티 클래스: 앱의 다양한 부분에서 사용되는 공유 클래스입니다(예: 여러 화면에서 사용되는 스와이프 새로고침 레이아웃).

데이터 레이어 (.data)

이 앱에는 remote 패키지의 시뮬레이션된 네트워킹 레이어와 local 패키지의 데이터베이스 레이어가 포함되어 있습니다. 간단하게 하기 위해 이 프로젝트에서는 실제 네트워크 요청을 만드는 대신 지연이 있는 HashMap만으로 네트워킹 레이어를 시뮬레이션합니다.

DefaultTasksRepository는 네트워킹 레이어와 데이터베이스 레이어 간에 조정하거나 중재하며 UI 레이어에 데이터를 반환합니다.

UI 레이어 ( .addedittask, .statistics, .taskdetail, .tasks)

각 UI 레이어 패키지에는 프래그먼트와 뷰 모델, UI에 필요한 기타 클래스 (예: 작업 목록의 어댑터)가 포함되어 있습니다. TaskActivity는 모든 프래그먼트를 포함하는 활동입니다.

탐색

앱의 탐색은 탐색 구성요소에 의해 제어됩니다. 이 쿼리는 nav_graph.xml 파일에서 정의됩니다. 탐색은 Event 클래스를 사용하여 뷰 모델에서 트리거되며, 뷰 모델은 전달할 인수를 결정합니다. 프래그먼트는 Event를 관찰하고 화면 간 실제 탐색을 실행합니다.

이 작업에서는 첫 번째 테스트를 실행합니다.

  1. Android 스튜디오에서 Project 창을 열고 다음 세 폴더를 찾습니다.
  • com.example.android.architecture.blueprints.todoapp
  • com.example.android.architecture.blueprints.todoapp (androidTest)
  • com.example.android.architecture.blueprints.todoapp (test)

이러한 폴더를 소스 세트라고 합니다. 소스 세트는 앱의 소스 코드가 포함된 폴더입니다. 녹색 (androidTesttest)으로 표시된 소스 세트에는 테스트가 포함되어 있습니다. 새 Android 프로젝트를 만들면 기본적으로 다음 세 가지 소스 세트가 제공됩니다. 각 필터는 다음과 같습니다.

  • main: 앱 코드를 포함합니다. 이 코드는 빌드할 수 있는 모든 다양한 버전의 앱 (빌드 변형이라고 함) 간에 공유됩니다.
  • androidTest: 계측 테스트라고 하는 테스트가 포함되어 있습니다.
  • test: 로컬 테스트라고 하는 테스트가 포함되어 있습니다.

로컬 테스트계측 테스트의 차이점은 실행 방식에 있습니다.

로컬 테스트 (test 소스 세트)

이러한 테스트는 개발 머신의 JVM에서 로컬로 실행되며 에뮬레이터나 실제 기기가 필요하지 않습니다. 이러한 이유로 빠르게 실행되지만 충실도가 낮아 실제와 같은 방식으로 작동하지 않습니다.

Android 스튜디오에서 로컬 테스트는 녹색 및 빨간색 삼각형 아이콘으로 표시됩니다.

계측 테스트 (androidTest 소스 세트)

이러한 테스트는 실제 또는 에뮬레이트된 Android 기기에서 실행되므로 실제 상황을 반영하지만 속도는 훨씬 느립니다.

Android 스튜디오에서 계측 테스트는 녹색 및 빨간색 삼각형 아이콘이 있는 Android로 표시됩니다.

1단계: 로컬 테스트 실행

  1. ExampleUnitTest.kt 파일을 찾을 때까지 test 폴더를 엽니다.
  2. 마우스 오른쪽 버튼으로 클릭하고 Run ExampleUnitTest를 선택합니다.

화면 하단의 Run 창에 다음과 같은 출력이 표시됩니다.

  1. 녹색 체크표시를 확인하고 테스트 결과를 펼쳐 addition_isCorrect라는 테스트 하나가 통과했는지 확인합니다. 더하기가 예상대로 작동해서 다행입니다.

2단계: 테스트 실패시키기

아래는 방금 실행한 테스트입니다.

ExampleUnitTest.kt

// A test class is just a normal class
class ExampleUnitTest {

   // Each test is annotated with @Test (this is a Junit annotation)
   @Test
   fun addition_isCorrect() {
       // Here you are checking that 4 is the same as 2+2
       assertEquals(4, 2 + 2)
   }
}

테스트는

  • 테스트 소스 세트 중 하나의 클래스입니다.
  • @Test 주석으로 시작하는 함수를 포함합니다 (각 함수는 단일 테스트임).
  • 일반적으로 어설션 문을 포함합니다.

Android에서는 테스트 라이브러리 JUnit (이 Codelab에서는 JUnit4)를 사용하여 테스트합니다. 어설션과 @Test 주석은 모두 JUnit에서 제공됩니다.

어설션은 테스트의 핵심입니다. 코드 또는 앱이 예상대로 작동했는지 확인하는 코드 문입니다. 이 경우 4가 2 + 2와 같은지 확인하는 assertEquals(4, 2 + 2)가 어설션입니다.

실패한 테스트가 어떻게 표시되는지 확인하려면 쉽게 실패할 수 있는 어설션을 추가하세요. 3이 1+1과 같은지 확인합니다.

  1. addition_isCorrect 테스트에 assertEquals(3, 1 + 1) 추가

ExampleUnitTest.kt

class ExampleUnitTest {

   // Each test is annotated with @Test (this is a Junit annotation)
   @Test
   fun addition_isCorrect() {
       assertEquals(4, 2 + 2)
       assertEquals(3, 1 + 1) // This should fail
   }
}
  1. 테스트를 실행합니다.
  1. 테스트 결과에서 테스트 옆에 X가 표시됩니다.

  1. 또한 다음 사항에 유의하세요.
  • 하나의 어설션이 실패하면 전체 테스트가 실패합니다.
  • 예상 값 (3)과 실제로 계산된 값 (2)이 표시됩니다.
  • 실패한 어설션 (ExampleUnitTest.kt:16)의 줄로 이동합니다.

3단계: 계측 테스트 실행

계측 테스트는 androidTest 소스 세트에 있습니다.

  1. androidTest 소스 세트를 엽니다.
  2. ExampleInstrumentedTest라는 테스트를 실행합니다.

ExampleInstrumentedTest

@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // Context of the app under test.
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        assertEquals("com.example.android.architecture.blueprints.reactive",
            appContext.packageName)
    }
}

로컬 테스트와 달리 이 테스트는 기기에서 실행됩니다 (아래 예에서는 에뮬레이트된 Pixel 2 휴대전화).

연결된 기기가 있거나 에뮬레이터가 실행 중인 경우 에뮬레이터에서 테스트가 실행되는 것을 확인할 수 있습니다.

이 작업에서는 앱의 활성 및 완료된 작업 통계 비율을 계산하는 getActiveAndCompleteStats에 대한 테스트를 작성합니다. 이러한 수치는 앱의 통계 화면에서 확인할 수 있습니다.

1단계: 테스트 클래스 만들기

  1. main 소스 세트의 todoapp.statistics에서 StatisticsUtils.kt를 엽니다.
  2. getActiveAndCompletedStats 함수를 찾습니다.

StatisticsUtils.kt

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

   val totalTasks = tasks!!.size
   val numberOfActiveTasks = tasks.count { it.isActive }
   val activePercent = 100 * numberOfActiveTasks / totalTasks
   val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks

   return StatsResult(
       activeTasksPercent = activePercent.toFloat(),
       completedTasksPercent = completePercent.toFloat()
   )
  
}

data class StatsResult(val activeTasksPercent: Float, val completedTasksPercent: Float)

getActiveAndCompletedStats 함수는 작업 목록을 허용하고 StatsResult를 반환합니다. StatsResult완료된 작업의 비율과 활성 작업의 비율이라는 두 숫자가 포함된 데이터 클래스입니다.

Android 스튜디오는 이 함수의 테스트를 구현하는 데 도움이 되는 테스트 스텁을 생성하는 도구를 제공합니다.

  1. getActiveAndCompletedStats를 마우스 오른쪽 버튼으로 클릭하고 Generate > Test를 선택합니다.

테스트 만들기 대화상자가 열립니다.

  1. 클래스 이름:StatisticsUtilsTest로 변경합니다 (StatisticsUtilsKtTest 대신). 테스트 클래스 이름에 KT가 없는 것이 약간 더 좋습니다.
  2. 나머지 기본값은 유지합니다. JUnit 4가 적절한 테스트 라이브러리입니다. 대상 패키지가 올바르고 (StatisticsUtils 클래스의 위치를 반영함) 체크박스를 선택할 필요가 없습니다 (체크박스를 선택하면 추가 코드가 생성되지만 테스트는 처음부터 작성할 예정임).
  3. 확인을 누릅니다.

대상 디렉터리 선택 대화상자가 열립니다.

함수가 수학 계산을 수행하고 Android 관련 코드를 포함하지 않으므로 로컬 테스트를 실행합니다. 따라서 실제 기기나 에뮬레이터에서 실행할 필요가 없습니다.

  1. 로컬 테스트를 작성할 것이므로 test 디렉터리 (androidTest 아님)를 선택합니다.
  2. 확인을 클릭합니다.
  3. test/statistics/.

    에 생성된 StatisticsUtilsTest 클래스를 확인합니다.

2단계: 첫 번째 테스트 함수 작성

다음과 같은 사항을 확인하는 테스트를 작성합니다.

  • 완료된 작업이 없고 활성 작업이 하나인 경우
  • 활성 테스트의 비율이 100%인 경우
  • 완료된 작업의 비율은 0%입니다.
  1. StatisticsUtilsTest를 엽니다.
  2. getActiveAndCompletedStats_noCompleted_returnsHundredZero이라는 함수를 만듭니다.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
        // Create an active task

        // Call your function

        // Check the result
    }
}
  1. 함수 이름 위에 @Test 주석을 추가하여 테스트임을 나타냅니다.
  2. 할 일 목록을 만듭니다.
// Create an active task 
val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
  1. 이러한 태스크로 getActiveAndCompletedStats를 호출합니다.
// Call your function
val result = getActiveAndCompletedStats(tasks)
  1. 어설션을 사용하여 result이 예상한 대로인지 확인합니다.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

전체 코드는 다음과 같습니다.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {

        // Create an active task (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertEquals(result.completedTasksPercent, 0f)
        assertEquals(result.activeTasksPercent, 100f)
    }
}
  1. 테스트를 실행합니다 (StatisticsUtilsTest를 마우스 오른쪽 버튼으로 클릭하고 실행 선택).

통과해야 합니다.

3단계: Hamcrest 종속 항목 추가

테스트는 코드의 기능을 문서화하는 역할을 하므로 사람이 읽을 수 있는 것이 좋습니다. 다음 두 가지 어설션을 비교해 보세요.

assertEquals(result.completedTasksPercent, 0f)

// versus

assertThat(result.completedTasksPercent, `is`(0f))

두 번째 주장은 훨씬 더 사람의 문장처럼 읽힙니다. Hamcrest라는 어설션 프레임워크를 사용하여 작성됩니다. 읽기 쉬운 어설션을 작성하는 데 유용한 또 다른 도구는 Truth 라이브러리입니다. 이 Codelab에서는 Hamcrest를 사용하여 어설션을 작성합니다.

  1. build.grade (Module: app)를 열고 다음 종속 항목을 추가합니다.

app/build.gradle

dependencies {
    // Other dependencies
    testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
}

일반적으로 종속 항목을 추가할 때는 implementation를 사용하지만 여기서는 testImplementation를 사용합니다. 앱을 전 세계와 공유할 준비가 되면 앱의 테스트 코드나 종속성으로 APK 크기를 부풀리지 않는 것이 좋습니다. Gradle 구성을 사용하여 라이브러리를 기본 코드에 포함할지 테스트 코드에 포함할지 지정할 수 있습니다. 가장 일반적인 구성은 다음과 같습니다.

  • implementation: 종속 항목은 테스트 소스 세트를 비롯한 모든 소스 세트에서 사용할 수 있습니다.
  • testImplementation - 종속 항목은 테스트 소스 세트에서만 사용할 수 있습니다.
  • androidTestImplementation - 종속 항목은 androidTest 소스 세트에서만 사용할 수 있습니다.

어떤 구성을 사용하는지에 따라 종속 항목을 사용할 수 있는 위치가 정의됩니다. 다음과 같이 작성하는 경우

testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"

즉, Hamcrest는 테스트 소스 세트에서만 사용할 수 있습니다. 또한 Hamcrest가 최종 앱에 포함되지 않습니다.

4단계: Hamcrest를 사용하여 어설션 작성

  1. assertEquals 대신 Hamcrest의 assertThat를 사용하도록 getActiveAndCompletedStats_noCompleted_returnsHundredZero() 테스트를 업데이트합니다.
// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

// WITH
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))

메시지가 표시되면 import org.hamcrest.Matchers.`is` 가져오기를 사용할 수 있습니다.

최종 테스트는 아래 코드와 같습니다.

StatisticsUtilsTest.kt

import com.example.android.architecture.blueprints.todoapp.data.Task
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.Test

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {

        // Create an active tasks (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertThat(result.activeTasksPercent, `is`(100f))
        assertThat(result.completedTasksPercent, `is`(0f))

    }
}
  1. 업데이트된 테스트를 실행하여 여전히 작동하는지 확인합니다.

이 Codelab에서는 Hamcrest의 모든 세부사항을 다루지 않습니다. 자세히 알아보려면 공식 튜토리얼을 참고하세요.

이 작업은 연습을 위한 선택사항입니다.

이 작업에서는 JUnit과 Hamcrest를 사용하여 테스트를 더 많이 작성합니다. 테스트 기반 개발 프로그램 관행에서 파생된 전략을 사용하여 테스트도 작성합니다. 테스트 기반 개발(TDD)은 기능 코드를 먼저 작성하는 대신 테스트를 먼저 작성해야 한다는 프로그래밍 사고방식입니다. 그런 다음 테스트를 통과한다는 목표로 기능 코드를 작성합니다.

1단계: 테스트 작성

일반 작업 목록이 있는 경우 테스트를 작성합니다.

  1. 완료된 작업이 하나 있고 활성 작업이 없는 경우 activeTasks 비율은 0f이고 완료된 작업 비율은 100f이어야 합니다 .
  2. 완료된 작업이 2개이고 활성 작업이 3개인 경우 완료 비율은 40f이고 활성 비율은 60f입니다.

2단계: 버그 테스트 작성

작성된 getActiveAndCompletedStats 코드에 버그가 있습니다. 목록이 비어 있거나 null인 경우 발생하는 상황을 제대로 처리하지 않습니다. 두 경우 모두 백분율이 0이어야 합니다.

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

   val totalTasks = tasks!!.size
   val numberOfActiveTasks = tasks.count { it.isActive }
   val activePercent = 100 * numberOfActiveTasks / totalTasks
   val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks

   return StatsResult(
       activeTasksPercent = activePercent.toFloat(),
       completedTasksPercent = completePercent.toFloat()
   )
  
}

코드를 수정하고 테스트를 작성하려면 테스트 기반 개발을 사용합니다. 테스트 주도 개발은 다음 단계를 따릅니다.

  1. Given, When, Then 구조를 사용하고 규칙을 따르는 이름으로 테스트를 작성합니다.
  2. 테스트가 실패하는지 확인합니다.
  3. 테스트를 통과하는 최소한의 코드를 작성합니다.
  4. 모든 테스트에 대해 반복합니다.

버그를 수정하는 것부터 시작하는 대신 테스트를 먼저 작성합니다. 그런 다음 향후 이러한 버그가 실수로 다시 도입되지 않도록 보호하는 테스트가 있는지 확인할 수 있습니다.

  1. 빈 목록 (emptyList())이 있으면 두 비율이 모두 0f여야 합니다.
  2. 작업을 로드하는 중에 오류가 발생한 경우 목록은 null이고 두 비율은 모두 0f여야 합니다.
  3. 테스트를 실행하고 실패하는지 확인합니다.

3단계: 버그 수정

이제 테스트가 있으므로 버그를 수정합니다.

  1. tasksnull이거나 비어 있으면 0f을 반환하여 getActiveAndCompletedStats의 버그를 수정합니다.
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}
  1. 테스트를 다시 실행하고 모든 테스트가 통과하는지 확인합니다.

TDD를 따르고 테스트를 먼저 작성하면 다음을 보장할 수 있습니다.

  • 새 기능에는 항상 연결된 테스트가 있으므로 테스트는 코드의 기능을 문서화하는 역할을 합니다.
  • 테스트는 올바른 결과를 확인하고 이미 확인된 버그를 방지합니다.

해결 방법: 테스트 더 작성하기

다음은 모든 테스트와 해당 기능 코드입니다.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {
        val tasks = listOf(
            Task("title", "desc", isCompleted = false)
        )
        // When the list of tasks is computed with an active task
        val result = getActiveAndCompletedStats(tasks)

        // Then the percentages are 100 and 0
        assertThat(result.activeTasksPercent, `is`(100f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }

    @Test
    fun getActiveAndCompletedStats_noActive_returnsZeroHundred() {
        val tasks = listOf(
            Task("title", "desc", isCompleted = true)
        )
        // When the list of tasks is computed with a completed task
        val result = getActiveAndCompletedStats(tasks)

        // Then the percentages are 0 and 100
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(100f))
    }

    @Test
    fun getActiveAndCompletedStats_both_returnsFortySixty() {
        // Given 3 completed tasks and 2 active tasks
        val tasks = listOf(
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = false),
            Task("title", "desc", isCompleted = false)
        )
        // When the list of tasks is computed
        val result = getActiveAndCompletedStats(tasks)

        // Then the result is 40-60
        assertThat(result.activeTasksPercent, `is`(40f))
        assertThat(result.completedTasksPercent, `is`(60f))
    }

    @Test
    fun getActiveAndCompletedStats_error_returnsZeros() {
        // When there's an error loading stats
        val result = getActiveAndCompletedStats(null)

        // Both active and completed tasks are 0
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }

    @Test
    fun getActiveAndCompletedStats_empty_returnsZeros() {
        // When there are no tasks
        val result = getActiveAndCompletedStats(emptyList())

        // Both active and completed tasks are 0
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }
}

StatisticsUtils.kt

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}

테스트 작성 및 실행의 기본사항을 잘 학습했습니다. 다음으로 기본 ViewModelLiveData 테스트를 작성하는 방법을 알아봅니다.

Codelab의 나머지 부분에서는 대부분의 앱에서 공통적으로 사용되는 두 Android 클래스인 ViewModelLiveData의 테스트를 작성하는 방법을 알아봅니다.

먼저 TasksViewModel에 대한 테스트를 작성합니다.


뷰 모델에 모든 로직이 있고 저장소 코드를 사용하지 않는 테스트에 집중합니다. 저장소 코드에는 비동기 코드, 데이터베이스, 네트워크 호출이 포함되어 테스트 복잡성이 추가됩니다. 지금은 이를 피하고 저장소의 항목을 직접 테스트하지 않는 ViewModel 기능 테스트를 작성하는 데 집중합니다.



작성할 테스트에서는 addNewTask 메서드를 호출할 때 새 작업 창을 여는 Event이 실행되는지 확인합니다. 테스트할 앱 코드는 다음과 같습니다.

TasksViewModel.kt

fun addNewTask() {
   _newTaskEvent.value = Event(Unit)
}

1단계: TasksViewModelTest 클래스 만들기

StatisticsUtilTest에 대해 수행한 것과 동일한 단계에 따라 이 단계에서는 TasksViewModelTest의 테스트 파일을 만듭니다.

  1. tasks 패키지에서 테스트할 클래스를 엽니다. TasksViewModel.
  2. 코드에서 클래스 이름 TasksViewModel을 마우스 오른쪽 버튼으로 클릭하고 생성 -> 테스트를 선택합니다.

  1. 테스트 만들기 화면에서 확인을 클릭하여 수락합니다 (기본 설정을 변경할 필요는 없음).
  2. Choose Destination Directory 대화상자에서 test 디렉터리를 선택합니다.

2단계: ViewModel 테스트 작성 시작

이 단계에서는 addNewTask 메서드를 호출할 때 새 작업 창을 여는 Event가 실행되는지 테스트하는 뷰 모델 테스트를 추가합니다.

  1. addNewTask_setsNewTaskEvent라는 새 테스트를 만듭니다.

TasksViewModelTest.kt

class TasksViewModelTest {

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh TasksViewModel


        // When adding a new task


        // Then the new task event is triggered

    }
    
}

애플리케이션 컨텍스트는 어떤가요?

테스트할 TasksViewModel 인스턴스를 만들 때 생성자에는 애플리케이션 컨텍스트가 필요합니다. 하지만 이 테스트에서는 활동과 UI, 프래그먼트가 있는 전체 애플리케이션을 만들지 않으므로 애플리케이션 컨텍스트를 어떻게 가져올 수 있을까요?

TasksViewModelTest.kt

// Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(???)

AndroidX 테스트 라이브러리에는 테스트용으로 설계된 애플리케이션 및 활동과 같은 구성요소 버전을 제공하는 클래스와 메서드가 포함되어 있습니다. 시뮬레이션된 Android 프레임워크 클래스(예: 애플리케이션 컨텍스트)가 필요한 로컬 테스트가 있는 경우 다음 단계에 따라 AndroidX 테스트를 올바르게 설정하세요.

  1. AndroidX Test 코어 및 ext 종속 항목 추가
  2. Robolectric 테스트 라이브러리 종속 항목 추가
  3. AndroidJunit4 테스트 실행기로 클래스에 주석을 답니다.
  4. AndroidX 테스트 코드 작성

이 단계를 완료하고 그런 다음 함께 수행하는 작업을 이해합니다.

3단계: Gradle 종속 항목 추가

  1. 이러한 종속 항목을 앱 모듈의 build.gradle 파일에 복사하여 핵심 AndroidX 테스트 핵심 및 ext 종속 항목과 Robolectric 테스트 종속 항목을 추가합니다.

app/build.gradle

    // AndroidX Test - JVM testing
testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"

    testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"

 testImplementation "org.robolectric:robolectric:$robolectricVersion"

4단계: JUnit 테스트 러너 추가

  1. 테스트 클래스 위에 @RunWith(AndroidJUnit4::class)를 추가합니다.

TasksViewModelTest.kt

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
    // Test code
}

5단계: AndroidX 테스트 사용

이제 AndroidX 테스트 라이브러리를 사용할 수 있습니다. 여기에는 애플리케이션 컨텍스트를 가져오는 ApplicationProvider.getApplicationContext 메서드가 포함됩니다.

  1. AndroidX 테스트 라이브러리에서 ApplicationProvider.getApplicationContext()를 사용하여 TasksViewModel를 만듭니다.

TasksViewModelTest.kt

// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
  1. tasksViewModel번으로 addNewTask님에게 전화 걸기

TasksViewModelTest.kt

tasksViewModel.addNewTask()

이제 테스트는 아래 코드와 같이 표시됩니다.

TasksViewModelTest.kt

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }
  1. 테스트를 실행하여 작동하는지 확인합니다.

개념: AndroidX 테스트는 어떻게 작동하나요?

AndroidX Test란 무엇인가요?

AndroidX 테스트는 테스트를 위한 라이브러리 모음입니다. 여기에는 테스트용으로 설계된 애플리케이션 및 활동과 같은 구성요소의 버전을 제공하는 클래스와 메서드가 포함되어 있습니다. 예를 들어 작성한 이 코드는 애플리케이션 컨텍스트를 가져오는 AndroidX 테스트 함수의 예입니다.

ApplicationProvider.getApplicationContext()

AndroidX 테스트 API의 장점 중 하나는 로컬 테스트 계측 테스트 모두에서 작동하도록 빌드된다는 것입니다. 이 기능이 유용한 이유는 다음과 같습니다.

  • 동일한 테스트를 로컬 테스트 또는 계측 테스트로 실행할 수 있습니다.
  • 로컬 테스트와 계측 테스트에 대해 서로 다른 테스트 API를 학습할 필요가 없습니다.

예를 들어 AndroidX Test 라이브러리를 사용하여 코드를 작성했으므로 TasksViewModelTest 클래스를 test 폴더에서 androidTest 폴더로 이동해도 테스트가 계속 실행됩니다. getApplicationContext()는 로컬 테스트로 실행되는지 계측 테스트로 실행되는지에 따라 약간 다르게 작동합니다.

  • 계측 테스트인 경우 에뮬레이터를 부팅하거나 실제 기기에 연결할 때 제공되는 실제 애플리케이션 컨텍스트를 가져옵니다.
  • 로컬 테스트인 경우 시뮬레이션된 Android 환경을 사용합니다.

Robolectric이란 무엇인가요?

AndroidX 테스트에서 로컬 테스트에 사용하는 시뮬레이션된 Android 환경은 Robolectric에서 제공합니다. Robolectric은 테스트를 위해 시뮬레이션된 Android 환경을 만들고 에뮬레이터를 부팅하거나 기기에서 실행하는 것보다 빠르게 실행되는 라이브러리입니다. Robolectric 종속 항목이 없으면 다음 오류가 발생합니다.

@RunWith(AndroidJUnit4::class)의 역할은 무엇인가요?

테스트 실행기 는 테스트를 실행하는 JUnit 구성요소입니다. 테스트 실행기가 없으면 테스트가 실행되지 않습니다. JUnit에서 제공하는 기본 테스트 실행기가 자동으로 제공됩니다. @RunWith은 기본 테스트 실행기를 대체합니다.

AndroidJUnit4 테스트 실행기를 사용하면 계측 테스트인지 로컬 테스트인지에 따라 AndroidX 테스트를 다르게 실행할 수 있습니다.

6단계: Robolectric 경고 수정

코드를 실행하면 Robolectric이 사용되는 것을 확인할 수 있습니다.

AndroidX Test 및 AndroidJunit4 테스트 러너 덕분에 Robolectric 코드를 직접 한 줄도 작성하지 않고도 이 작업을 수행할 수 있습니다.

두 가지 경고가 표시될 수 있습니다.

  • No such manifest file: ./AndroidManifest.xml
  • "WARN: Android SDK 29 requires Java 9..."

gradle 파일을 업데이트하여 No such manifest file: ./AndroidManifest.xml 경고를 수정할 수 있습니다.

  1. 올바른 Android 매니페스트가 사용되도록 gradle 파일에 다음 줄을 추가합니다. includeAndroidResources 옵션을 사용하면 AndroidManifest 파일을 비롯한 단위 테스트의 Android 리소스에 액세스할 수 있습니다.

app/build.gradle

    // Always show the result of every unit test when running via command line, even if it passes.
    testOptions.unitTests {
        includeAndroidResources = true

        // ... 
    }

"WARN: Android SDK 29 requires Java 9..." 경고는 더 복잡합니다. Android Q에서 테스트를 실행하려면 Java 9가 필요합니다. Java 9를 사용하도록 Android 스튜디오를 구성하는 대신 이 Codelab에서는 타겟 및 컴파일 SDK를 28로 유지합니다.

요약하면 다음과 같습니다.

  • 순수 뷰 모델 테스트는 일반적으로 test 소스 세트에 배치할 수 있습니다. 이러한 테스트의 코드에는 일반적으로 Android가 필요하지 않기 때문입니다.
  • AndroidX 테스트라이브러리를 사용하여 애플리케이션 및 활동과 같은 구성요소의 테스트 버전을 가져올 수 있습니다.
  • test 소스 세트에서 시뮬레이션된 Android 코드를 실행해야 하는 경우 Robolectric 종속 항목과 @RunWith(AndroidJUnit4::class) 주석을 추가하면 됩니다.

축하합니다. AndroidX 테스트 라이브러리와 Robolectric을 모두 사용하여 테스트를 실행하고 있습니다. 테스트가 완료되지 않았습니다 (아직 assert 문을 작성하지 않았으며 // TODO test LiveData라고만 표시됨). 다음으로 LiveData를 사용하여 assert 문을 작성하는 방법을 알아봅니다.

이 작업에서는 LiveData 값을 올바르게 어설션하는 방법을 알아봅니다.

addNewTask_setsNewTaskEvent 뷰 모델 테스트 없이 중단한 부분입니다.

TasksViewModelTest.kt

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }
    

LiveData를 테스트하려면 다음 두 가지 작업을 수행하는 것이 좋습니다.

  1. InstantTaskExecutorRule 사용
  2. LiveData 관찰 보장

1단계: InstantTaskExecutorRule 사용

InstantTaskExecutorRuleJUnit 규칙입니다. @get:Rule 주석과 함께 사용하면 InstantTaskExecutorRule 클래스의 일부 코드가 테스트 전후에 실행됩니다 (정확한 코드를 보려면 단축키 Command+B를 사용하여 파일을 볼 수 있음).

이 규칙은 테스트 결과가 동기식으로 반복 가능한 순서로 발생하도록 동일한 스레드에서 모든 아키텍처 구성요소 관련 백그라운드 작업을 실행합니다. LiveData 테스트를 포함하는 테스트를 작성할 때는 이 규칙을 사용하세요.

  1. 이 규칙이 포함된 Architecture Components 핵심 테스트 라이브러리의 Gradle 종속 항목을 추가합니다.

app/build.gradle

testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
  1. TasksViewModelTest.kt 열기
  2. TasksViewModelTest 클래스 내에 InstantTaskExecutorRule를 추가합니다.

TasksViewModelTest.kt

class TasksViewModelTest {
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()
    
    // Other code...
}

2단계: LiveDataTestUtil.kt 클래스 추가

다음 단계는 테스트 중인 LiveData가 관찰되는지 확인하는 것입니다.

LiveData을 사용할 때는 일반적으로 활동 또는 프래그먼트 (LifecycleOwner)가 LiveData을 관찰합니다.

viewModel.resultLiveData.observe(fragment, Observer {
    // Observer code here
})

이 관찰은 중요합니다. LiveData에서 다음 작업을 하려면 활성 관찰자가 필요합니다.

  • onChanged 이벤트를 트리거하지 않습니다.
  • 변환을 트리거합니다.

뷰 모델의 LiveData에 대해 예상되는 LiveData 동작을 얻으려면 LifecycleOwnerLiveData를 관찰해야 합니다.

여기서 문제가 발생합니다. TasksViewModel 테스트에서는 LiveData을 관찰할 활동이나 프래그먼트가 없습니다. 이 문제를 해결하려면 LifecycleOwner이 필요 없이 LiveData가 지속적으로 관찰되도록 하는 observeForever 메서드를 사용하면 됩니다. observeForever할 때는 관찰자를 삭제해야 합니다. 그렇지 않으면 관찰자 누수가 발생할 수 있습니다.

아래 코드와 비슷합니다. 다음과 같이 검사합니다.

@Test
fun addNewTask_setsNewTaskEvent() {

    // Given a fresh ViewModel
    val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())


    // Create observer - no need for it to do anything!
    val observer = Observer<Event<Unit>> {}
    try {

        // Observe the LiveData forever
        tasksViewModel.newTaskEvent.observeForever(observer)

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.value
        assertThat(value?.getContentIfNotHandled(), (not(nullValue())))

    } finally {
        // Whatever happens, don't forget to remove the observer!
        tasksViewModel.newTaskEvent.removeObserver(observer)
    }
}

테스트에서 단일 LiveData를 관찰하기에는 상용구 코드가 너무 많습니다. 이 상용구를 없애는 방법은 몇 가지가 있습니다. 관찰자 추가를 더 간단하게 하기 위해 LiveDataTestUtil라는 확장 함수를 만듭니다.

  1. test 소스 세트에 LiveDataTestUtil.kt라는 새 Kotlin 파일을 만듭니다.


  1. 아래 코드를 복사하여 붙여넣습니다.

LiveDataTestUtil.kt

import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException


@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS,
    afterObserve: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(o: T?) {
            data = o
            latch.countDown()
            this@getOrAwaitValue.removeObserver(this)
        }
    }
    this.observeForever(observer)

    try {
        afterObserve.invoke()

        // Don't wait indefinitely if the LiveData is not set.
        if (!latch.await(time, timeUnit)) {
            throw TimeoutException("LiveData value was never set.")
        }

    } finally {
        this.removeObserver(observer)
    }

    @Suppress("UNCHECKED_CAST")
    return data as T
}

이 방법은 상당히 복잡합니다. 관찰자를 추가하고 LiveData 값을 가져온 다음 관찰자를 정리하는 getOrAwaitValue이라는 Kotlin 확장 함수를 만듭니다. 기본적으로 위에 표시된 observeForever 코드의 짧고 재사용 가능한 버전입니다. 이 클래스에 대한 자세한 설명은 이 블로그 게시물을 참고하세요.

3단계: getOrAwaitValue를 사용하여 어설션 작성

이 단계에서는 getOrAwaitValue 메서드를 사용하고 newTaskEvent가 트리거되었는지 확인하는 assert 문을 작성합니다.

  1. getOrAwaitValue를 사용하여 newTaskEventLiveData 값을 가져옵니다.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
  1. 값이 null이 아닌지 어설션합니다.
assertThat(value.getContentIfNotHandled(), (not(nullValue())))

완성된 테스트는 아래 코드와 같습니다.

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.android.architecture.blueprints.todoapp.getOrAwaitValue
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.nullValue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()


    @Test
    fun addNewTask_setsNewTaskEvent() {
        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.getOrAwaitValue()

        assertThat(value.getContentIfNotHandled(), not(nullValue()))


    }

}
  1. 코드를 실행하고 테스트가 통과하는지 확인합니다.

테스트 작성 방법을 확인했으니 직접 테스트를 작성해 보세요. 이 단계에서는 학습한 기술을 사용하여 다른 TasksViewModel 테스트를 작성하는 연습을 합니다.

1단계: ViewModel 테스트 직접 작성

setFilterAllTasks_tasksAddViewVisible()를 작성합니다. 이 테스트에서는 필터 유형을 모든 작업을 표시하도록 설정한 경우 작업 추가 버튼이 표시되는지 확인해야 합니다.

  1. addNewTask_setsNewTaskEvent()를 참조하여 필터링 모드를 ALL_TASKS로 설정하고 tasksAddViewVisible LiveData가 true인지 확인하는 TasksViewModelTestsetFilterAllTasks_tasksAddViewVisible()라는 테스트를 작성합니다.


시작하려면 아래 코드를 사용하세요.

TasksViewModelTest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel

        // When the filter type is ALL_TASKS

        // Then the "Add task" action is visible
        
    }

참고

  • 모든 작업의 TasksFilterType 열거형은 ALL_TASKS.입니다.
  • 태스크 추가 버튼의 공개 상태는 LiveData tasksAddViewVisible.에 의해 제어됩니다.
  1. 테스트를 실행합니다.

2단계: 테스트를 솔루션과 비교

아래의 솔루션과 내 솔루션을 비교합니다.

TasksViewModelTest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.getOrAwaitValue(), `is`(true))
    }

다음 사항을 확인하세요.

  • 동일한 AndroidX ApplicationProvider.getApplicationContext() 문을 사용하여 tasksViewModel를 만듭니다.
  • ALL_TASKS 필터 유형 enum을 전달하여 setFiltering 메서드를 호출합니다.
  • getOrAwaitNextValue 메서드를 사용하여 tasksAddViewVisible이 true인지 확인합니다.

3단계: @Before 규칙 추가

두 테스트의 시작 부분에서 TasksViewModel를 정의하는 방법을 확인하세요.

TasksViewModelTest

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

여러 테스트에 대해 설정 코드가 반복되는 경우 @Before 주석을 사용하여 설정 메서드를 만들고 반복되는 코드를 삭제할 수 있습니다. 이러한 모든 테스트는 TasksViewModel를 테스트하고 뷰 모델이 필요하므로 이 코드를 @Before 블록으로 이동합니다.

  1. tasksViewModel|이라는 lateinit 인스턴스 변수를 만듭니다.
  2. setupViewModel 메서드를 만듭니다.
  3. @Before로 주석을 답니다.
  4. 뷰 모델 인스턴스화 코드를 setupViewModel로 이동합니다.

TasksViewModelTest

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }
  1. 코드를 실행합니다.

경고

다음과 같이 하면 안 됩니다 .

tasksViewModel

정의는 다음과 같습니다.

val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

이렇게 하면 모든 테스트에 동일한 인스턴스가 사용됩니다. 각 테스트에는 테스트 대상 (이 경우 ViewModel)의 새 인스턴스가 있어야 하므로 이렇게 하면 안 됩니다.

TasksViewModelTest의 최종 코드는 아래 코드와 같이 표시됩니다.

TasksViewModelTest

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    // Executes each task synchronously using Architecture Components.
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }


    @Test
    fun addNewTask_setsNewTaskEvent() {

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.awaitNextValue()
        assertThat(
            value?.getContentIfNotHandled(), (not(nullValue()))
        )
    }

    @Test
    fun getTasksAddViewVisible() {

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.awaitNextValue(), `is`(true))
    }
    
}

여기를 클릭하여 시작한 코드와 최종 코드의 차이를 확인하세요.

완료된 Codelab의 코드를 다운로드하려면 아래의 git 명령어를 사용하면 됩니다.

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout end_codelab_1


또는 ZIP 파일로 저장소를 다운로드한 다음 압축을 풀고 Android 스튜디오에서 열어도 됩니다.

ZIP 파일 다운로드

이 Codelab에서는 다음 내용을 다루었습니다.

  • Android 스튜디오에서 테스트를 실행하는 방법
  • 로컬 (test) 테스트와 계측 테스트 (androidTest)의 차이점
  • JUnitHamcrest를 사용하여 로컬 단위 테스트를 작성하는 방법
  • AndroidX 테스트 라이브러리를 사용하여 ViewModel 테스트를 설정합니다.

Udacity 과정:

Android 개발자 문서:

동영상:

기타:

이 과정의 다른 Codelab 링크는 Kotlin 기반 Android 고급 Codelab 방문 페이지를 참고하세요.