#1 안드로이드 앱 테스트
#1-1 안드로이드 앱 테스트의 종류
먼저, 여기에 있는 구글 공식 문서에서 안드로이드 앱 테스트에 대한 개요를 읽으면 좋다. 해당 구글 공식 문서에서 복사해온 위의 그림은 테스트를 총 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
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
#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
Truth
Mockito
Robolectric
Glide
#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의 기초
#5-2 ViewModel 테스트
#5-3 Room, LiveData 테스트
#5-4 Retrofit 테스트
'깨알 개념 > Android' 카테고리의 다른 글
[Android] Unit Testing - ViewModel (0) | 2024.07.08 |
---|---|
[Android] Unit Testing - 기초 (0) | 2024.07.08 |
[Android] Dagger2 - Application 활용하기 (0) | 2024.06.25 |
[Android] Dagger2 - Activity에 Dependency 주입 (0) | 2024.06.25 |
[Android] Dagger2 - 인터페이스 구현체 주입 (@Binds) (0) | 2024.06.25 |