테스트 기본사항

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

소개

첫 번째 앱의 첫 번째 기능을 구현할 때 코드가 올바르게 작동했는지 확인하기 위해 코드를 실행했을 가능성이 높습니다. 수동 테스트이지만 테스트를 실행했습니다. 기능을 계속 추가하고 업데이트하면서 코드를 계속 실행하고 작동하는지를 확인할 수도 있습니다. 하지만 매번 수동으로 진행하는 것은 지루하고 실수가 발생하기 때문에 확장되지 않습니다.

컴퓨터는 확장과 자동화에 매우 효과적입니다. 따라서 대규모 및 소규모 회사의 개발자는 자동 테스트를 작성합니다. 이 테스트는 소프트웨어에서 실행되며 테스트 시 수동으로 작동하여 코드가 작동하는지 확인할 필요가 없는 테스트입니다.

이 Codelab 시리즈에서 배울 내용은 실제 앱을 위한 테스트 모음 (테스트 모음이라고 함)을 만드는 방법입니다.

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

기본 요건

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

학습할 내용

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

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

다음 라이브러리와 코드 개념에 대해 알아봅니다.

실습할 내용

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

일련의 Codelab에서는 할 일 메모 앱을 사용하여 작업합니다. 이 앱을 사용하여 완료할 작업을 작성하고 목록에 표시할 수 있습니다. 그런 다음 완료로 표시하거나 필터링하거나 삭제할 수 있습니다.

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

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

ZIP 파일 다운로드

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

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

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

1단계: 샘플 앱 실행

할 일 앱을 다운로드한 후 Android 스튜디오에서 앱을 열고 실행합니다. 컴파일됩니다. 다음 단계에 따라 앱을 탐색합니다.

  • 더하기 작업 버튼을 사용하여 새 작업을 만듭니다. 먼저 제목을 입력하고 할 일에 대한 추가 정보를 입력합니다. 녹색 체크표시 FAB로 저장합니다.
  • 할 일 목록에서 방금 완료한 할 일의 제목을 클릭하고 할 일의 세부정보 화면을 통해 설명의 나머지 부분을 확인합니다.
  • 목록이나 세부정보 화면에서 작업의 체크박스를 선택하여 완료됨으로 설정합니다.
  • 할 일 화면으로 돌아가서 필터 메뉴를 열고 활성완료 상태로 할 일을 필터링합니다.
  • 탐색 창을 열고 통계를 클릭합니다.
  • 개요 화면으로 돌아가서 탐색 창 메뉴에서 완료를 선택하여 완료됨 상태의 모든 작업을 삭제합니다.

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

TO-DO 앱은 인기 있는 아키텍처 청사진 테스트 및 아키텍처 샘플 (샘플의 반응형 아키텍처 버전 사용)을 기반으로 합니다. 앱은 앱 아키텍처 가이드의 아키텍처를 따릅니다. 프래그먼트, 저장소, 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 소스 세트)

이러한 테스트는 개발 머신의 로컬에서 실행되고 에뮬레이터나 실제 기기가 필요하지 않습니다. 이로 인해 빠르게 실행되지만 충실도가 낮습니다. 즉, 실제에서 하는 동작보다 적게 작동합니다.

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에서 가져옵니다.

어설션은 테스트의 핵심입니다. 코드나 앱이 예상대로 동작하는지 확인하는 코드 문입니다. 이 경우 어설션은 assertEquals(4, 2 + 2) 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 함수를 찾습니다.

StatsUtils.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를 선택합니다.

Create Test 대화상자가 열립니다.

  1. 클래스 이름:StatisticsUtilsTest로 변경합니다(StatisticsUtilsKtTest 대신, 테스트 클래스 이름에 KT를 포함하지 않는 것이 더 좋음).
  2. 나머지 기본값을 유지합니다. JUnit 4가 적절한 테스트 라이브러리입니다. 대상 패키지가 올바르며 (StatisticsUtils 클래스의 위치를 미러링함) 체크박스는 선택하지 않아도 됩니다. 체크박스만 추가하면 되지만 (이 경우 추가 코드가 생성되지만 테스트를 처음부터 작성합니다.)
  3. 확인을 누릅니다.

Choose Destination Directory 대화상자가 열립니다.

함수가 수학 계산을 수행하고 있고 Android 전용 코드를 포함하지 않으므로 로컬 테스트를 진행합니다. 따라서 실제 기기 또는 에뮬레이션된 기기에서 실행할 필요가 없습니다.

  1. 로컬 테스트를 작성 중일 것이므로 androidTest가 아닌 test 디렉터리를 선택합니다.
  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를 마우스 오른쪽 버튼으로 클릭하고 Run 선택).

통과해야 하는 항목:

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. Hamcrest's assertThatassertEquals 사용할 수 있도록 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. 주어진, 시점, 이후 구조를 사용하고 규칙에 따라 이름을 지정하여 테스트를 작성합니다.
  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))
    }
}

StatsUtils.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 -> Generate -> Test를 마우스 오른쪽 버튼으로 클릭합니다.

  1. Create Test 화면에서 OK를 클릭하여 수락합니다 (기본 설정은 변경하지 않아도 됨).
  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 테스트란 무엇인가요?

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

ApplicationProvider.getApplicationContext()

AndroidX Test API의 이점 중 하나는 로컬 테스트 계측 테스트 모두에서 작동하도록 빌드되었다는 점입니다. 다음과 같은 이유입니다.

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

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

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

Robolectric이란 무엇인가요?

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

@RunWith(AndroidJUnit4::class)의 역할

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

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

6단계: Robolectric 경고 수정

코드를 실행하면 Robolectric이 사용됩니다.

AndroidX 테스트와 AndroidJunit4 테스트 실행기이므로 Robolectric 코드를 한 줄도 직접 작성하지 않고도 이 작업이 실행됩니다.

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

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

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

  1. gradle 파일에 다음 줄을 추가하여 올바른 Android 매니페스트가 사용되도록 합니다. 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에서 테스트를 실행하려면 자바 9가 필요합니다. 자바 9를 사용하도록 Android 스튜디오를 구성하는 대신 이 Codelab에서는 대상을 유지하고 SDK 28에서 컴파일합니다.

요약:

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

수고하셨습니다. AndroidX 테스트 라이브러리와 Robolectric을 모두 사용하여 테스트를 실행하고 있습니다. 테스트가 완료되지 않았습니다. 아직 어설션 문을 작성하지 않았습니다. 방금 // TODO test LiveData입니다. 다음에는 LiveData로 어설션 문을 작성하는 방법을 알아봅니다.

이 작업에서는 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. 아키텍처 구성요소 핵심 테스트 라이브러리 (이 규칙이 포함된)의 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 동작을 가져오려면 LifecycleOwner를 사용하여 LiveData를 관찰해야 합니다.

이로 인해 문제가 발생합니다. 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가 트리거되었는지 확인하는 어설션 문을 작성합니다.

  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()를 사용하고 TasksViewModelTest에서 setFilterAllTasks_tasksAddViewVisible()라는 테스트를 작성합니다. 이때 필터링 모드를 ALL_TASKS으로 설정하고 tasksAddViewVisible LiveData가 true라고 어설션합니다.


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

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를 만듭니다.
  • setFiltering 메서드를 호출하여 ALL_TASKS 필터 유형 enum을 전달합니다.
  • 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 Codelab의 고급 Android 방문 페이지를 참고하세요.