깨알 개념/Android

[Android] Unit Testing - 개요와 환경 설정

interfacer_han 2024. 7. 3. 16:20

#1 안드로이드 앱 테스트

#1-1 안드로이드 앱 테스트의 종류

http://https//developer.android.com/training/testing/fundamentals#scope

먼저, 여기에 있는 구글 공식 문서에서 안드로이드 앱 테스트에 대한 개요를 읽으면 좋다. 해당 구글 공식 문서에서 복사해온 위의 그림은 테스트를 총 3단계로 나누고 있다. 먼저 Unit test는 함수나 클래스 등의 앱의 아주 작은 부분을 검증하는 테스트고, 본 게시글에서 다룰 내용이다. 두번째는 Integration(통합) test로, 데이터베이스 연동, API 호출 등 모듈 간의 상호작용을 검증한다. 마지막은 End-to-end(끝과 끝을 붙이는) test다. 전체 시스템의 모든 구성 요소() 간의 상호작용(잘 붙는 지)을 확인한다. End-to-end test에서의 '구성 요소'는 사용자의 입력이나 시나리오 등까지 포함한다. Integration test와 이름의 늬앙스는 비슷하지만, 아주 넓은 범위를 테스트한다.
 

#1-2 안드로이드 에뮬레이터와 Unit Testing

안드로이드 입문자 입장에서, 안드로이드 프로젝트에서의 테스트는 기본적으로 Android Virtual Device를 통한 확인이다. 이는 #1-1의 그림에서 End-to-End test의 한 종류라고 볼 수 있다. 하지만 이는 사용자 경험 위주의 확인인데다가, 필요없는 부분까지 포함해 테스트하게 된다. 즉, 앱을 통째로 테스트하는 어찌보면 약간 무식한 방법이다. 이때 안드로이드에선 또 다른 테스트 방법을 제공하는데, 이 방법들은 프로젝트를 부분(Unit)적으로 테스트하게 만들어준다.
 

#2 안드로이드 Unit Testing의 종류

#2-1 Local unit test

 

로컬 단위 테스트 빌드  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 로컬 단위 테스트 빌드 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 로컬 테스트는 Android 기기나 에

developer.android.com

JVM(Java Virtual Machine)을 사용하여 빠른 테스트를 실행한다. 즉, Android 에뮬레이터와는 관련이 없다. 그렇다고 Android 프레임워크와 관련된 테스트를 할 수 없다는 말은 아니다. Context와 같이 Android의 실제 런타임 환경과 관련된 개념에 대한 테스트 등은 진행하지 못하지만, ViewModel과 같이 UI 및 안드로이드 프레임워크와의 의존성이 적은 객체는 테스트가 가능하다. 왜냐하면 필요한 경우 Dependency를 Test double로 둬버리면 그만이기 때문이다. 게다가, Robolectric나 Mockito와 같은 Unit Test용 라이브러리는 Android 프레임워크의 일부를 모방하거나 상호작용를 가능케 만들어주어 Local unit test의 한계를 꽤나 줄여준다. 그러나 이렇게해도 테스트할 수 없는 상황이라면 아래에 있는 Instrumented test로 테스트한다. Local unit test 수행을 위해 존재하는 "프로젝트명/모듈명/src/test/java"라는 전용 디렉토리가 안드로이드 프로젝트에 있다.
 

#2-2 Instrumented test

 

계측 테스트 빌드  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 계측 테스트 빌드 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 계측 테스트는 실제 기기 또는 에뮬

developer.android.com

#2-1 달리, Android 기기에서 실행된다. 따라서 Android 프레임워크 API를 직접 활용할 수 있다. 따라서 Local unit test에 비해 더 높은 신뢰성 & 충실도를 지닌다. 하지만 훨씬 느리므로 Local unit test가 불가능한 때의 선택지로 두는 것이 좋다. Instrumented test를 위해 존재하는 Instrumentation라는 클래스가 있다. 이를 통해 애플리케이션을 모니터링하면서 Activity, Service, BroadcastReceiver 등을 실행하거나, 기기의 버튼 클릭이나 스크린 터치 등의 사용자 입력 또한 시뮬레이션 가능하다. Instrumented test 수행을 위해 존재하는 "프로젝트명/모듈명/src/androidTest/java"라는 전용 디렉토리가 안드로이드 프로젝트에 있다.
 

#3 Unit test를 위한 라이브러리 다운로드

#3-1 dependency의 종류

plugins {
    ...
}

android {
    ...
}

dependencies {

    implementation("androidx.core:core-ktx:1.9.0")
    implementation("androidx.appcompat:appcompat:1.7.0")
    implementation("com.google.android.material:material:1.12.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.2.1")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
}

안드로이드 스튜디오에서 [File] - [New] - [New Project] - [Empty Views Activity]할 때, 모듈 수준 build.gradle.kts에 기본적으로 들어가있는 dependency다. 큰 따옴표(") 사이에 있는 종속성의 문자열 식별자 그리고 그 종속성의 구현 방법을 정의한다. 그런데, 종속성의 구현 방법이 하나가 아닌 모습을 볼 수 있다. 여기서는 implementation, testImplementation, androidTestImplementation의 3가지로 범주가 나뉘어져 있다.
 
implementation은 프로젝트의 모든 곳에서 종속성(Dependency)을 제공한다. testImplementation은 Local unit test에게만 종속성을 제공한다. 이 말은, 프로젝트명/모듈명/src/test/java의 디렉토리에서만 통하는 종속성이라는 이야기다. 프로젝트를 APK로 만들어 Play Store에 올릴 때 이 testImplementation은 (APK의 용량 낭비를 막기 위해) 포함되지 않는다. androidTestImplementation은 Instrumented test에게만 종속성을 제공한다. 이 말은, 프로젝트명/모듈명/src/androidTest/java의 디렉토리에서만 통하는 종속성이라는 이야기다. 프로젝트를 APK로 만들어 Play Store에 올릴 때 이 androidTestImplementation은 (APK의 용량 낭비를 막기 위해) 포함되지 않는다.
 

#3-2 Gradle 버전 및 AGP 버전 설정

[File] - [Project Structure...]에서 프로젝트의 Gradle 버전 및 AGP(Android Gradle Plugin) 버전을 각각 8.0 및 8.1.2로 선택한다.
 

#3-3 프로젝트 수준 build.gradle 준비

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id("com.android.application") version "8.1.2" apply false
    id("org.jetbrains.kotlin.android") version "1.9.0" apply false

    // KSP (Annotation 읽기)
    id("com.google.devtools.ksp") version "1.9.0-1.0.13" apply false
}

 

#3-4 모듈 수준 build.gradle 준비

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")

    // kapt, KSP (Annotation 읽기)
    id("kotlin-kapt")
    id("com.google.devtools.ksp")
}

android {
    namespace = "com.example.setupenvironment"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.setupenvironment"
        minSdk = 26
        targetSdk = 33
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = "17"
    }
    buildFeatures {
        buildConfig = true // BuildConfig 클래스가 필요할 시
        dataBinding = true // 데이터 바인딩 시
    }
}

dependencies {

    // 프로젝트 생성 시 built-in 되어있던 Dependency들 (범주화를 위해, 이 중 일부는 아래쪽 코드로 옮겼음)
    implementation("androidx.core:core-ktx:1.9.0")
    implementation("androidx.appcompat:appcompat:1.7.0")
    implementation("com.google.android.material:material:1.12.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") // Android용으로 특별히 만들어진 사용자 인터페이스 테스트 프레임워크

    // JUnit: 자바와 안드로이드 개발에서 표준으로 사용되는 테스트 프레임워크. 다른 Dependency들의 기반으로도 널리 쓰인다
    testImplementation("junit:junit:4.13.2") // 프로젝트 생성 시 built-in 되어있던 Dependency.
    androidTestImplementation("androidx.test.ext:junit:1.2.1") // 프로젝트 생성 시 built-in 되어있던 Dependency. JUnit을 확장해 안드로이드에서 테스트할 수 있게 해줌
    testImplementation("androidx.test.ext:junit:1.2.1") // JUnit을 확장해 안드로이드에서 테스트할 수 있게 해줌 (Local unit test용)

    // Lifecycle
    val lifecycleVersion = "2.8.3"
    testImplementation("androidx.lifecycle:lifecycle-runtime-testing:$lifecycleVersion") // Test용 LifecycleOwner 등 제공

    // LiveData
    val archVersion = "2.2.0"
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion")
    testImplementation("androidx.arch.core:core-testing:$archVersion") // Test helpers for LiveData (테스트에 LiveData를 사용하는 경우)
    androidTestImplementation("androidx.arch.core:core-testing:$archVersion") // Test helpers for LiveData (테스트에 LiveData를 사용하는 경우) (Instrumented test용)

    // ViewModel
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion")
    implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycleVersion") // ViewModel의 상태 관리를 돕는 라이브러리

    // Coroutines
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")

    // Dagger
    val daggerVersion = "2.51.1"
    implementation("com.google.dagger:dagger:$daggerVersion")
    kapt("com.google.dagger:dagger-compiler:$daggerVersion") // Dagger의 Annotation 구문을 읽기 위한 종속성

    // Retrofit
    val retrofitVersion = "2.11.0"
    val okhttp3Version = "4.12.0"
    implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
    implementation("com.squareup.retrofit2:converter-gson:$retrofitVersion") // JSON 컨버터
    implementation("com.squareup.okhttp3:logging-interceptor:$okhttp3Version") // 웹 통신 로그
    testImplementation("com.squareup.okhttp3:mockwebserver:$okhttp3Version") // 웹 서버 Mock 생성

    // Room
    val roomVersion = "2.6.1"
    implementation("androidx.room:room-runtime:$roomVersion")
    kapt("androidx.room:room-compiler:$roomVersion") // Room의 Annotation 구문을 읽기 위한 종속성
    implementation("androidx.room:room-ktx:$roomVersion") // Room에서 사용할 수 있는 Coroutines 클래스 라이브러리

    // Truth: 테스트 성공 및 실패 메시지를 더 읽기 쉽게 만들어주는 라이브러리
    val truthVersion = "1.4.3"
    testImplementation("com.google.truth:truth:$truthVersion")
    testImplementation("com.google.truth.extensions:truth-java8-extension:$truthVersion") // Truth가 Java 8에 도입된 기능을 이용해 테스트하도록 만들어주는 추가 라이브러리

    androidTestImplementation("com.google.truth:truth:$truthVersion")
    androidTestImplementation("com.google.truth.extensions:truth-java8-extension:$truthVersion") // Truth가 Java 8에 도입된 기능을 이용해 테스트하도록 만들어주는 추가 라이브러리 (Instrumented test용)

    // (JUnit에 기반) Mockito: Test double의 일종인 Mock 생성을 도와주는 라이브러리
    testImplementation("org.mockito:mockito-core:5.12.0")
    testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1") // 좀 더 'Kotlin스러운' 방식으로 Mock 객체를 생성하고 사용할 수 있게 도와주는 추가 라이브러리

    // (JUnit에 기반) Robolectric: 안드로이드 에뮬레이터를 모방해 빠르게 안드로이드 환경을 시뮬레이션. 즉, 원래라면 Instrumented test를 진행해야되는 걸 Local unit test로 진행하게 만들어줌
    testImplementation("org.robolectric:robolectric:4.12.2")

    // (JUnit에 기반) Glide: 이미지 불러오기 및 관리
    implementation("com.github.bumptech.glide:glide:4.16.0")
}

#5에 있는 이어지는 글들에서 사용할 라이브러리 및 테스트용 라이브러리들을 전부 포함했다. testImplement( ... )에 비해 androidTestImplement( ... )의 갯수가 적은 것은 androidTestImplement( ... )의 특성에 기인한다. 바로, 테스트 시에 안드로이드 프레임워크를 사용한다는 점 말이다. 덕분에 프로젝트 전역에 Dependency를 제공하는 Implement( ... )만으로 종속성이 충족된 것이다. compileOptions 및 kotlinOptions의 JDK 버전이 17임에 유의하자.
 
라이브러리 링크 모음

더보기

JUnit4

 

JUnit – About

JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks. @Test public void newArrayListsHaveNoElements() { assertThat(new ArrayList ().size(), is(0)); } @Test public void sizeReturnsNum

junit.org

 

Truth

 

Truth - Fluent assertions for Java and Android

What is Truth? Truth is a library for performing assertions in tests: assertThat(notificationText).contains("testuser@google.com"); Truth is owned and maintained by the Guava team. It is used in the majority of the tests in Google’s own codebase. Why use

truth.dev

 

Mockito

 

Mockito framework site

Intro Why How More Who Links Training Why drink it? Mockito is a mocking framework that tastes really good. It lets you write beautiful tests with a clean & simple API. Mockito doesn’t give you hangover because the tests are very readable and they produc

site.mockito.org

 

Robolectric

 

Robolectric

test-drive your Android code Running tests on an Android emulator or device is slow! Building, deploying, and launching the app often takes a minute or more. That's no way to do TDD. There must be a better way. Robolectric is a framework that brings fast a

robolectric.org

 

Glide

 

Glide v4 : Fast and efficient image loading for Android

About Glide Glide is a fast and efficient image loading library for Android focused on smooth scrolling. Glide offers an easy to use API, a performant and extensible resource decoding pipeline and automatic resource pooling. Glide supports fetching, decodi

bumptech.github.io

 

#3-5 Gradle이 사용할 JDK 버전 지정

[File] - [Setting...] - [Build, Execution, Deployment] - [Build Tools] - [Gradle]에서 Gradle이 사용할 JDK를 지정한다. General settings에서는 JDK 17의 디렉토리를 지정한다. Gradle projects에서는 Gradle JDK를 설정한다. 이 때 이 게시글을 참조해 JDK 17 설치 및 환경 변수 JAVA_HOME을 설정하고 JAVA_HOME을 선택하거나, 이미 설치된 17버전의 JDK 디렉토리를 선택한다. 후자의 방법이 더 간편하다. 만약 목록에 '이미 설치된 17버전의 JDK 디렉토리'가 보이지 않는다면, [Download JDK...]를 눌러 다운로드하면 된다.
 

#4 요약

Unit Test를 통해 AVD로 일일히 확인하는 시간 낭비를 줄인다.

#5 이어지는 글

#5-1 Android Unit test의 기초 

 

[Android] Unit Testing - 기초

#1 이전 글#1-1 Unit Testing 개요 [Android] Unit Testing - 개요와 환경 설정#1 안드로이드 앱 테스트#1-1 안드로이드 앱 테스트의 종류먼저, 여기에 있는 구글 공식 문서에서 안드로이드 앱 테스트에 대한

kenel.tistory.com

 

#5-2 ViewModel 테스트 

 

[Android] Unit Testing - ViewModel

#1 이전 글#1-1 Unit Testing 개요 [Android] Unit Testing - 개요와 환경 설정#1 안드로이드 앱 테스트#1-1 안드로이드 앱 테스트의 종류먼저, 여기에 있는 구글 공식 문서에서 안드로이드 앱 테스트에 대한

kenel.tistory.com

 

#5-3 Room, LiveData 테스트

 

[Android] Unit Testing - Room과 LiveData

#1 이전 글#1-1 Unit Testing 개요 [Android] Unit Testing - 개요와 환경 설정#1 안드로이드 앱 테스트#1-1 안드로이드 앱 테스트의 종류먼저, 여기에 있는 구글 공식 문서에서 안드로이드 앱 테스트에 대한

kenel.tistory.com

 

#5-4 Retrofit 테스트

 

[Android] Unit Testing - Retrofit

#1 이전 글#1-1 Unit Testing 개요 [Android] Unit Testing - 개요와 환경 설정#1 안드로이드 앱 테스트#1-1 안드로이드 앱 테스트의 종류먼저, 여기에 있는 구글 공식 문서에서 안드로이드 앱 테스트에 대한

kenel.tistory.com