1. Sebelum memulai
Codelab ini mengajarkan cara menulis pengujian unit untuk menguji komponen ViewModel
. Anda akan menambahkan pengujian unit untuk aplikasi game Unscramble. Aplikasi Unscramble adalah game kata yang menyenangkan dan mengharuskan pengguna untuk menebak kata yang ejaannya diacak dan memperoleh poin jika menebak dengan benar. Gambar berikut menampilkan pratinjau aplikasi:
Di codelab Menulis pengujian otomatis, Anda telah mempelajari pengertian pengujian otomatis dan alasan pentingnya pengujian tersebut. Anda juga telah mempelajari cara menerapkan pengujian unit.
Anda telah mempelajari:
- Pengujian otomatis merupakan kode yang memverifikasi keakuratan bagian kode lainnya.
- Pengujian adalah bagian penting dari proses pengembangan aplikasi. Dengan menjalankan pengujian terhadap aplikasi secara konsisten, Anda dapat memverifikasi perilaku fungsional dan kegunaan aplikasi sebelum merilisnya ke publik.
- Dengan pengujian unit, Anda bisa menguji fungsi, class, dan properti.
- Pengujian unit lokal dijalankan di workstation Anda, yang berarti pengujian tersebut berjalan di lingkungan pengembangan tanpa memerlukan emulator atau perangkat Android. Dengan kata lain, pengujian lokal berjalan di komputer Anda.
Sebelum melanjutkan, pastikan Anda menyelesaikan codelab Menulis pengujian otomatis serta ViewModel dan Status dalam Compose.
Prasyarat
- Pengetahuan tentang Kotlin, termasuk fungsi, lambda, dan composable stateless
- Pengetahuan dasar tentang cara membuat tata letak di Jetpack Compose
- Pengetahuan dasar tentang Desain Material
- Pengetahuan dasar tentang cara mengimplementasikan ViewModel
Yang akan Anda pelajari
- Cara menambahkan dependensi untuk pengujian unit dalam file
build.gradle.kts
modul aplikasi - Cara membuat strategi pengujian untuk mengimplementasikan pengujian unit
- Cara menulis pengujian unit menggunakan JUnit4 dan memahami siklus proses instance pengujian
- Cara menjalankan, menganalisis, dan meningkatkan cakupan kode
Yang akan Anda bangun
- Pengujian unit untuk aplikasi game Unscramble
Yang akan Anda butuhkan
- Versi terbaru Android Studio
Mendapatkan kode awal
Untuk memulai, download kode awal:
Atau, Anda dapat membuat clone repositori GitHub untuk kode tersebut:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-unscramble.git $ cd basic-android-kotlin-compose-training-unscramble $ git checkout viewmodel
Anda dapat menjelajahi kode di repositori GitHub Unscramble
.
2. Ringkasan kode awal
Di Unit 2, Anda telah mempelajari cara menempatkan kode pengujian unit di set sumber test yang berada di folder src, seperti yang ditunjukkan pada gambar berikut:
Kode awal memiliki file berikut:
WordsData.kt
: File ini berisi daftar kata yang akan digunakan untuk pengujian dan fungsi bantuangetUnscrambledWord()
untuk mendapatkan kata yang tidak diacak dari kata acak. Anda tidak perlu mengubah file ini.
3. Menambahkan dependensi pengujian
Dalam codelab ini, Anda akan menggunakan framework JUnit untuk menulis pengujian unit. Untuk menggunakan framework tersebut, Anda harus menambahkannya sebagai dependensi dalam file build.gradle.kts
modul aplikasi.
Gunakan konfigurasi implementation
untuk menentukan dependensi yang diperlukan aplikasi Anda. Misalnya, untuk menggunakan library ViewModel
di aplikasi, Anda harus menambahkan dependensi ke androidx.lifecycle:lifecycle-viewmodel-compose
seperti yang ditunjukkan dalam cuplikan kode berikut:
dependencies {
...
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
}
Sekarang Anda dapat menggunakan library ini dalam kode sumber aplikasi, dan Android Studio akan membantu menambahkannya ke File Paket Aplikasi (APK) yang dihasilkan. Namun, Anda tidak ingin kode pengujian unit menjadi bagian dari file APK. Kode pengujian tidak menambahkan fungsi apa pun yang akan digunakan pengguna, dan kode tersebut juga memiliki dampak terhadap ukuran APK. Hal yang sama berlaku untuk dependensi yang diperlukan oleh kode pengujian Anda; Anda harus memisahkannya. Untuk melakukannya, gunakan konfigurasi testImplementation
, yang menunjukkan bahwa konfigurasi berlaku untuk kode sumber pengujian lokal, bukan kode aplikasi.
Untuk menambahkan dependensi ke project Anda, tetapkan konfigurasi dependensi (seperti implementation
atau testImplementation
) dalam blok dependensi file build.gradle.kts
Anda. Setiap konfigurasi dependensi memberi Gradle petunjuk yang berbeda tentang cara menggunakan dependensi.
Untuk menambahkan dependensi:
- Buka file
build.gradle.kts
modulapp
, yang terletak di direktoriapp
di panel Project.
- Di dalam file, scroll ke bawah hingga Anda menemukan blok
dependencies{}
. Tambahkan dependensi menggunakan konfigurasitestImplementation
untukjunit
.
plugins {
...
}
android {
...
}
dependencies {
...
testImplementation("junit:junit:4.13.2")
}
- Di baris notifikasi pada bagian atas file build.gradle.kts, klik Sync Now agar impor dan build selesai seperti yang ditunjukkan pada screenshot berikut:
Bill of Materials (BOM) Compose
BOM Compose adalah cara yang direkomendasikan untuk mengelola versi library Compose. Dengan BOM, Anda dapat mengelola semua versi library Compose hanya dengan menentukan versi BOM.
Perhatikan bagian dependensi dalam file build.gradle.kts
modul app
.
// No need to copy over
// This is part of starter code
dependencies {
// Import the Compose BOM
implementation (platform("androidx.compose:compose-bom:2023.06.01"))
...
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
...
}
Perhatikan hal-hal berikut:
- Nomor versi library Compose tidak ditentukan.
- BOM diimpor menggunakan
implementation platform("androidx.compose:compose-bom:2023.06.01")
Hal ini karena BOM sendiri memiliki link ke berbagai library Compose versi stabil terbaru sehingga berfungsi dapat bersama dengan baik. Saat menggunakan BOM di aplikasi, Anda tidak perlu menambahkan versi apa pun ke dependensi library Compose itu sendiri. Saat Anda mengupdate versi BOM, semua library yang digunakan akan otomatis diupdate ke versi terbaru.
Untuk bisa menggunakan BOM dengan library pengujian compose (pengujian berinstrumen), Anda harus mengimpor androidTestImplementation platform("androidx.compose:compose-bom:xxxx.xx.xx")
. Anda dapat membuat variabel dan menggunakannya kembali untuk implementation
dan androidTestImplementation
seperti yang ditunjukkan.
// Example, not need to copy over
dependencies {
// Import the Compose BOM
implementation(platform("androidx.compose:compose-bom:2023.06.01"))
implementation("androidx.compose.material:material")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
// ...
androidTestImplementation(platform("androidx.compose:compose-bom:2023.06.01"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
}
Bagus. Anda berhasil menambahkan dependensi pengujian ke aplikasi dan mempelajari BOM. Sekarang Anda siap menambahkan beberapa pengujian unit.
4. Strategi pengujian
Strategi pengujian yang baik berfokus pada mencakup berbagai jalur dan batas kode Anda. Pada tingkat paling dasar, Anda dapat mengategorikan pengujian dalam tiga skenario: jalur berhasil, jalur error, dan kasus batas.
- Jalur berhasil: Pengujian jalur berhasil - juga dikenal sebagai pengujian happy path, berfokus pada pengujian fungsi untuk alur yang positif. Alur positif adalah alur yang tidak memiliki pengecualian atau kondisi error. Dibandingkan dengan skenario error dan skenario kasus batas, sangat mudah untuk membuat daftar lengkap skenario jalur berhasil karena berfokus pada perilaku yang diinginkan untuk aplikasi Anda.
Contoh jalur berhasil di aplikasi Unscramble adalah update skor, jumlah kata, dan kata yang ejaannya benar saat pengguna memasukkan kata yang benar dan mengklik tombol Submit.
- Jalur error: Pengujian jalur error berfokus pada pengujian fungsi untuk alur negatif–yaitu, untuk memeriksa cara aplikasi merespons kondisi error atau input pengguna yang tidak valid. Menentukan semua kemungkinan alur error cukup sulit karena ada banyak kemungkinan hasil jika perilaku yang diinginkan tidak tercapai.
Salah satu saran umum adalah mencantumkan semua kemungkinan jalur error, menulis pengujian untuknya, dan membuat pengujian unit terus berkembang saat Anda menemukan berbagai skenario.
Contoh jalur error di aplikasi Unscramble adalah pengguna memasukkan kata yang salah dan mengklik tombol Submit, yang menyebabkan pesan error muncul dan skor serta jumlah kata tidak diperbarui.
- Kasus batas: Kasus batas berfokus pada pengujian kondisi batas dalam aplikasi. Di aplikasi Unscramble, batas adalah memeriksa status UI saat aplikasi dimuat dan status UI setelah pengguna memainkan jumlah kata maksimum.
Membuat skenario pengujian seputar kategori ini dapat berfungsi sebagai panduan untuk rencana pengujian Anda.
Membuat pengujian
Pengujian unit yang baik biasanya memiliki empat properti berikut:
- Fokus: Pengujian harus berfokus pada pengujian unit, seperti potongan kode. Potongan kode ini sering kali berupa class atau metode. Pengujian harus lebih spesifik dan berfokus pada validasi kebenaran setiap potongan kode, bukan beberapa potongan kode secara bersamaan.
- Dapat dipahami: Kode harus sederhana dan mudah dipahami saat Anda membaca kode. Secara sekilas, developer harus dapat segera memahami maksud di balik pengujian.
- Deterministik: Daftar harus berhasil atau gagal secara konsisten. Saat Anda menjalankan pengujian beberapa kali, tanpa mengubah kode, pengujian akan memberikan hasil yang sama. Pengujian harus stabil, dengan kegagalan dalam satu instance dan berhasil pada instance lain meskipun tidak ada modifikasi pada kode.
- Mandiri: Tidak memerlukan interaksi atau penyiapan manusia dan berjalan secara terpisah.
Jalur berhasil
Untuk menulis pengujian unit bagi jalur berhasil, Anda perlu menyatakan bahwa dengan inisialisasi instance GameViewModel
, saat metode updateUserGuess()
dipanggil dengan kata tebakan yang benar diikuti dengan panggilan ke metode checkUserGuess()
, maka:
- Tebakan yang benar akan diteruskan ke metode
updateUserGuess()
. - Metode ini disebut
checkUserGuess()
. - Nilai untuk status
score
danisGuessedWordWrong
diperbarui dengan benar.
Selesaikan langkah-langkah berikut untuk membuat pengujian:
- Buat paket baru
com.example.android.unscramble.ui.test
di set sumber pengujian dan tambahkan file seperti yang ditampilkan dalam screenshot berikut:
Untuk menulis pengujian unit class GameViewModel
, Anda memerlukan instance class agar dapat memanggil metode class dan memverifikasi statusnya.
- Di bagian isi class
GameViewModelTest
, deklarasikan propertiviewModel
dan tetapkan instance classGameViewModel
ke dalamnya.
class GameViewModelTest {
private val viewModel = GameViewModel()
}
- Untuk menulis pengujian unit bagi jalur berhasil, buat fungsi
gameViewModel_CorrectWordGuessed_ScoreUpdatedAndErrorFlagUnset()
dan anotasikan dengan anotasi@Test
.
class GameViewModelTest {
private val viewModel = GameViewModel()
@Test
fun gameViewModel_CorrectWordGuessed_ScoreUpdatedAndErrorFlagUnset() {
}
}
- Impor hal berikut:
import org.junit.Test
Untuk meneruskan kata pemain yang benar ke metode viewModel.updateUserGuess()
, Anda harus mendapatkan kata tidak diacak yang benar dari kata acak di GameUiState
. Untuk melakukannya, dapatkan status UI game saat ini terlebih dahulu.
- Di bagian isi fungsi, buat variabel
currentGameUiState
, dan tetapkanviewModel.uiState.value
ke variabel tersebut.
@Test
fun gameViewModel_CorrectWordGuessed_ScoreUpdatedAndErrorFlagUnset() {
var currentGameUiState = viewModel.uiState.value
}
- Untuk mendapatkan tebakan pemain yang benar, gunakan fungsi
getUnscrambledWord()
, yang menggunakancurrentGameUiState.currentScrambledWord
sebagai argumen dan menampilkan kata yang tidak diacak. Simpan nilai yang ditampilkan ini dalam variabel hanya baca baru bernamacorrectPlayerWord
dan tetapkan nilai yang ditampilkan oleh fungsigetUnscrambledWord()
.
@Test
fun gameViewModel_CorrectWordGuessed_ScoreUpdatedAndErrorFlagUnset() {
var currentGameUiState = viewModel.uiState.value
val correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)
}
- Untuk memverifikasi apakah kata yang ditebak sudah benar, tambahkan panggilan ke metode
viewModel.updateUserGuess()
dan teruskan variabelcorrectPlayerWord
sebagai argumen. Kemudian, tambahkan panggilan ke metodeviewModel.checkUserGuess()
untuk memverifikasi tebakan.
@Test
fun gameViewModel_CorrectWordGuessed_ScoreUpdatedAndErrorFlagUnset() {
var currentGameUiState = viewModel.uiState.value
val correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)
viewModel.updateUserGuess(correctPlayerWord)
viewModel.checkUserGuess()
}
Anda sekarang siap untuk menyatakan bahwa status game adalah yang Anda harapkan.
- Dapatkan instance class
GameUiState
dari nilai propertiviewModel.uiState
dan simpan dalam variabelcurrentGameUiState
.
@Test
fun gameViewModel_CorrectWordGuessed_ScoreUpdatedAndErrorFlagUnset() {
var currentGameUiState = viewModel.uiState.value
val correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)
viewModel.updateUserGuess(correctPlayerWord)
viewModel.checkUserGuess()
currentGameUiState = viewModel.uiState.value
}
- Untuk memeriksa apakah kata yang ditebak sudah benar dan skor diperbarui, gunakan fungsi
assertFalse()
untuk memverifikasi bahwa properticurrentGameUiState.isGuessedWordWrong
adalahfalse
dan fungsiassertEquals()
untuk memverifikasi bahwa nilai properticurrentGameUiState.score
sama dengan20
.
@Test
fun gameViewModel_CorrectWordGuessed_ScoreUpdatedAndErrorFlagUnset() {
var currentGameUiState = viewModel.uiState.value
val correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)
viewModel.updateUserGuess(correctPlayerWord)
viewModel.checkUserGuess()
currentGameUiState = viewModel.uiState.value
// Assert that checkUserGuess() method updates isGuessedWordWrong is updated correctly.
assertFalse(currentGameUiState.isGuessedWordWrong)
// Assert that score is updated correctly.
assertEquals(20, currentGameUiState.score)
}
- Impor hal berikut:
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
- Agar nilai
20
dapat dibaca dan digunakan kembali, buat objek pendamping dan tetapkan20
ke konstantaprivate
yang bernamaSCORE_AFTER_FIRST_CORRECT_ANSWER
. Update pengujian dengan konstanta yang baru dibuat.
class GameViewModelTest {
...
@Test
fun gameViewModel_CorrectWordGuessed_ScoreUpdatedAndErrorFlagUnset() {
...
// Assert that score is updated correctly.
assertEquals(SCORE_AFTER_FIRST_CORRECT_ANSWER, currentGameUiState.score)
}
companion object {
private const val SCORE_AFTER_FIRST_CORRECT_ANSWER = SCORE_INCREASE
}
}
- Jalankan pengujian.
Pengujian seharusnya berhasil, karena semua pernyataan valid, seperti yang ditunjukkan pada screenshot berikut:
Jalur error
Guna menulis pengujian unit untuk jalur error, Anda perlu menyatakan bahwa saat kata yang salah diteruskan sebagai argumen ke metode viewModel.updateUserGuess()
dan metode viewModel.checkUserGuess()
dipanggil, hal berikut akan terjadi:
- Nilai properti
currentGameUiState.score
tidak berubah. - Nilai properti
currentGameUiState.isGuessedWordWrong
ditetapkan ketrue
karena tebakannya salah.
Selesaikan langkah-langkah berikut untuk membuat pengujian:
- Dalam isi class
GameViewModelTest
, buat fungsigameViewModel_IncorrectGuess_ErrorFlagSet()
dan anotasikan dengan anotasi@Test
.
@Test
fun gameViewModel_IncorrectGuess_ErrorFlagSet() {
}
- Tentukan variabel
incorrectPlayerWord
dan tetapkan nilai"and"
ke variabel tersebut, yang seharusnya tidak ada dalam daftar kata.
@Test
fun gameViewModel_IncorrectGuess_ErrorFlagSet() {
// Given an incorrect word as input
val incorrectPlayerWord = "and"
}
- Tambahkan panggilan ke metode
viewModel.updateUserGuess()
dan teruskan variabelincorrectPlayerWord
sebagai argumen. - Tambahkan panggilan ke metode
viewModel.checkUserGuess()
untuk memverifikasi tebakan.
@Test
fun gameViewModel_IncorrectGuess_ErrorFlagSet() {
// Given an incorrect word as input
val incorrectPlayerWord = "and"
viewModel.updateUserGuess(incorrectPlayerWord)
viewModel.checkUserGuess()
}
- Tambahkan variabel
currentGameUiState
dan tetapkan nilai statusviewModel.uiState.value
ke variabel tersebut. - Gunakan fungsi pernyataan untuk menyatakan bahwa nilai properti
currentGameUiState.score
adalah0
dan nilai properticurrentGameUiState.isGuessedWordWrong
disetel ketrue
.
@Test
fun gameViewModel_IncorrectGuess_ErrorFlagSet() {
// Given an incorrect word as input
val incorrectPlayerWord = "and"
viewModel.updateUserGuess(incorrectPlayerWord)
viewModel.checkUserGuess()
val currentGameUiState = viewModel.uiState.value
// Assert that score is unchanged
assertEquals(0, currentGameUiState.score)
// Assert that checkUserGuess() method updates isGuessedWordWrong correctly
assertTrue(currentGameUiState.isGuessedWordWrong)
}
- Impor hal berikut:
import org.junit.Assert.assertTrue
- Jalankan pengujian untuk mengonfirmasi bahwa pengujian berhasil.
Kasus batas
Untuk menguji status awal UI, Anda harus menulis pengujian unit untuk class GameViewModel
. Pengujian harus menyatakan bahwa jika GameViewModel
diinisialisasi, hal berikut berlaku:
- Properti
currentWordCount
diatur ke1
. - Properti
score
diatur ke0
. - Properti
isGuessedWordWrong
diatur kefalse
. - Properti
isGameOver
diatur kefalse
.
Selesaikan langkah-langkah berikut untuk menambahkan pengujian:
- Buat metode
gameViewModel_Initialization_FirstWordLoaded()
dan anotasikan dengan anotasi@Test
.
@Test
fun gameViewModel_Initialization_FirstWordLoaded() {
}
- Akses properti
viewModel.uiState.value
untuk mendapatkan instance awal classGameUiState
. Tetapkan ke variabel hanya bacagameUiState
yang baru.
@Test
fun gameViewModel_Initialization_FirstWordLoaded() {
val gameUiState = viewModel.uiState.value
}
- Untuk mendapatkan kata pemain yang benar, gunakan fungsi
getUnscrambledWord()
, yang menggunakan katagameUiState.currentScrambledWord
dan menampilkan kata yang tidak diacak. Tetapkan nilai yang ditampilkan ke variabel hanya baca baru yang bernamaunScrambledWord
.
@Test
fun gameViewModel_Initialization_FirstWordLoaded() {
val gameUiState = viewModel.uiState.value
val unScrambledWord = getUnscrambledWord(gameUiState.currentScrambledWord)
}
- Untuk memverifikasi bahwa status sudah benar, tambahkan fungsi
assertTrue()
untuk menyatakan bahwa properticurrentWordCount
disetel ke1
, dan propertiscore
disetel ke0
. - Tambahkan fungsi
assertFalse()
untuk memverifikasi bahwa propertiisGuessedWordWrong
adalahfalse
dan bahwa propertiisGameOver
disetel kefalse
.
@Test
fun gameViewModel_Initialization_FirstWordLoaded() {
val gameUiState = viewModel.uiState.value
val unScrambledWord = getUnscrambledWord(gameUiState.currentScrambledWord)
// Assert that current word is scrambled.
assertNotEquals(unScrambledWord, gameUiState.currentScrambledWord)
// Assert that current word count is set to 1.
assertTrue(gameUiState.currentWordCount == 1)
// Assert that initially the score is 0.
assertTrue(gameUiState.score == 0)
// Assert that the wrong word guessed is false.
assertFalse(gameUiState.isGuessedWordWrong)
// Assert that game is not over.
assertFalse(gameUiState.isGameOver)
}
- Impor hal berikut:
import org.junit.Assert.assertNotEquals
- Jalankan pengujian untuk mengonfirmasi bahwa pengujian berhasil.
Kasus batas lainnya adalah menguji status UI setelah pengguna menebak semua kata. Anda perlu menyatakan bahwa saat pengguna menebak semua kata dengan benar, hal berikut berlaku:
- Skornya merupakan yang terbaru;
- Properti
currentGameUiState.currentWordCount
sama dengan nilai konstantaMAX_NO_OF_WORDS
; - Properti
currentGameUiState.isGameOver
disetel ketrue
.
Selesaikan langkah-langkah berikut untuk menambahkan pengujian:
- Buat metode
gameViewModel_AllWordsGuessed_UiStateUpdatedCorrectly()
dan anotasikan dengan anotasi@Test
. Dalam metode ini, buat variabelexpectedScore
dan tetapkan0
ke variabel tersebut.
@Test
fun gameViewModel_AllWordsGuessed_UiStateUpdatedCorrectly() {
var expectedScore = 0
}
- Untuk mendapatkan status awal, tambahkan variabel
currentGameUiState
, lalu tetapkan nilai propertiviewModel.uiState.value
ke variabel tersebut.
@Test
fun gameViewModel_AllWordsGuessed_UiStateUpdatedCorrectly() {
var expectedScore = 0
var currentGameUiState = viewModel.uiState.value
}
- Untuk mendapatkan kata pemain yang benar, gunakan fungsi
getUnscrambledWord()
, yang menggunakan katacurrentGameUiState.currentScrambledWord
dan menampilkan kata yang tidak diacak. Simpan nilai yang ditampilkan ini dalam variabel hanya baca baru bernamacorrectPlayerWord
dan tetapkan nilai yang ditampilkan oleh fungsigetUnscrambledWord()
.
@Test
fun gameViewModel_AllWordsGuessed_UiStateUpdatedCorrectly() {
var expectedScore = 0
var currentGameUiState = viewModel.uiState.value
var correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)
}
- Untuk menguji apakah pengguna menebak semua jawaban, gunakan blok
repeat
untuk mengulangi eksekusi metodeviewModel.updateUserGuess()
dan metodeviewModel.checkUserGuess()
MAX_NO_OF_WORDS
kali.
@Test
fun gameViewModel_AllWordsGuessed_UiStateUpdatedCorrectly() {
var expectedScore = 0
var currentGameUiState = viewModel.uiState.value
var correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)
repeat(MAX_NO_OF_WORDS) {
}
}
- Di blok
repeat
, tambahkan nilai konstantaSCORE_INCREASE
ke variabelexpectedScore
untuk menyatakan bahwa skor akan bertambah untuk setiap jawaban benar. - Tambahkan panggilan ke metode
viewModel.updateUserGuess()
dan teruskan variabelcorrectPlayerWord
sebagai argumen. - Tambahkan panggilan ke metode
viewModel.checkUserGuess()
untuk memicu pemeriksaan tebakan pengguna.
@Test
fun gameViewModel_AllWordsGuessed_UiStateUpdatedCorrectly() {
var expectedScore = 0
var currentGameUiState = viewModel.uiState.value
var correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)
repeat(MAX_NO_OF_WORDS) {
expectedScore += SCORE_INCREASE
viewModel.updateUserGuess(correctPlayerWord)
viewModel.checkUserGuess()
}
}
- Perbarui kata pemain saat ini, gunakan fungsi
getUnscrambledWord()
, yang menggunakancurrentGameUiState.currentScrambledWord
sebagai argumen dan menampilkan kata yang tidak diacak. Simpan nilai yang ditampilkan ini dalam variabel hanya baca baru bernamacorrectPlayerWord.
Untuk memverifikasi bahwa statusnya sudah benar, tambahkan fungsiassertEquals()
untuk memeriksa apakah nilai properticurrentGameUiState.score
sama dengan nilaiexpectedScore
.
@Test
fun gameViewModel_AllWordsGuessed_UiStateUpdatedCorrectly() {
var expectedScore = 0
var currentGameUiState = viewModel.uiState.value
var correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)
repeat(MAX_NO_OF_WORDS) {
expectedScore += SCORE_INCREASE
viewModel.updateUserGuess(correctPlayerWord)
viewModel.checkUserGuess()
currentGameUiState = viewModel.uiState.value
correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)
// Assert that after each correct answer, score is updated correctly.
assertEquals(expectedScore, currentGameUiState.score)
}
}
- Tambahkan fungsi
assertEquals()
untuk menyatakan bahwa nilai properticurrentGameUiState.currentWordCount
sama dengan nilai konstantaMAX_NO_OF_WORDS
dan bahwa nilai properticurrentGameUiState.isGameOver
disetel ketrue
.
@Test
fun gameViewModel_AllWordsGuessed_UiStateUpdatedCorrectly() {
var expectedScore = 0
var currentGameUiState = viewModel.uiState.value
var correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)
repeat(MAX_NO_OF_WORDS) {
expectedScore += SCORE_INCREASE
viewModel.updateUserGuess(correctPlayerWord)
viewModel.checkUserGuess()
currentGameUiState = viewModel.uiState.value
correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)
// Assert that after each correct answer, score is updated correctly.
assertEquals(expectedScore, currentGameUiState.score)
}
// Assert that after all questions are answered, the current word count is up-to-date.
assertEquals(MAX_NO_OF_WORDS, currentGameUiState.currentWordCount)
// Assert that after 10 questions are answered, the game is over.
assertTrue(currentGameUiState.isGameOver)
}
- Impor hal berikut:
import com.example.unscramble.data.MAX_NO_OF_WORDS
- Jalankan pengujian untuk mengonfirmasi bahwa pengujian berhasil.
Ringkasan siklus proses instance pengujian
Jika melihat lebih dekat cara viewModel
melakukan inisialisasi dalam pengujian, Anda mungkin melihat bahwa viewModel
hanya melakukan inisialisasi satu kali meskipun semua pengujian menggunakannya. Cuplikan kode ini menunjukkan definisi properti viewModel
.
class GameViewModelTest {
private val viewModel = GameViewModel()
@Test
fun gameViewModel_Initialization_FirstWordLoaded() {
val gameUiState = viewModel.uiState.value
...
}
...
}
Anda mungkin bertanya-tanya:
- Apakah instance
viewModel
yang sama digunakan kembali untuk semua pengujian? - Apakah akan menimbulkan masalah? Misalnya, bagaimana jika metode pengujian
gameViewModel_Initialization_FirstWordLoaded
dieksekusi setelah metode pengujiangameViewModel_CorrectWordGuessed_ScoreUpdatedAndErrorFlagUnset
? Apakah pengujian inisialisasi akan gagal?
Jawaban untuk kedua pertanyaan tersebut adalah tidak. Metode pengujian dijalankan secara terpisah untuk menghindari efek samping yang tidak terduga dari status instance pengujian yang dapat diubah. Secara default, sebelum masing-masing metode pengujian dieksekusi, JUnit membuat instance class pengujian baru.
Karena sejauh ini Anda memiliki empat metode pengujian di class GameViewModelTest
, GameViewModelTest
akan membuat instance empat kali. Setiap instance memiliki salinan properti viewModel
-nya sendiri. Oleh karena itu, urutan eksekusi pengujian tidak penting.
5. Pengantar cakupan kode
Cakupan kode memainkan peran penting untuk menentukan apakah Anda telah secara memadai menguji class, metode, dan baris kode yang membentuk aplikasi Anda.
Android Studio menyediakan alat cakupan pengujian untuk pengujian unit lokal guna melacak persentase dan area kode aplikasi yang dicakup oleh pengujian unit Anda.
Menjalankan pengujian dengan cakupan menggunakan Android Studio
Untuk menjalankan pengujian dengan cakupan:
- Klik kanan file
GameViewModelTest.kt
di panel project, lalu pilih Run 'GameViewModelTest' with Coverage.
- Setelah eksekusi uji selesai, klik opsi Flatten Packages di sebelah kanan panel cakupan.
- Perhatikan paket
com.example.android.unscramble.ui
seperti yang ditunjukkan dalam gambar berikut.
- Klik dua kali nama paket
com.example.android.unscramble.ui
untuk menampilkan cakupanGameViewModel
seperti yang ditunjukkan dalam gambar berikut:
Menganalisis laporan pengujian
Laporan yang ditampilkan pada diagram berikut dibagi menjadi dua aspek:
- Persentase metode yang dicakup oleh pengujian unit: Dalam diagram contoh, pengujian yang Anda tulis sejauh ini mencakup 7 dari 8 metode. Jumlah ini berarti 87% dari total metode.
- Persentase baris yang dicakup oleh pengujian unit: Dalam contoh diagram, pengujian yang Anda tulis mencakup 39 dari 41 baris kode. Jumlah ini berarti 95% baris kode.
Laporan menunjukkan bahwa pengujian unit yang Anda tulis sejauh ini melewatkan bagian kode tertentu. Untuk mengetahui bagian yang terlewat, selesaikan langkah berikut:
- Klik dua kali GameViewModel.
Android Studio menampilkan file GameViewModel.kt
dengan coding warna tambahan di sisi kiri jendela. Warna hijau cerah menunjukkan bahwa baris kode tersebut tercakup.
Saat men-scroll ke bawah pada GameViewModel
, Anda mungkin melihat beberapa baris ditandai dengan warna merah muda cerah. Warna ini menunjukkan bahwa baris kode ini tidak tercakup oleh pengujian unit.
Meningkatkan cakupan
Untuk meningkatkan cakupan, Anda harus menulis pengujian yang mencakup jalur yang terlewat. Anda perlu menambahkan pengujian untuk menyatakan bahwa jika pengguna melewati kata, hal berikut berlaku:
- Properti
currentGameUiState.score
tetap tidak berubah. - Properti
currentGameUiState.currentWordCount
bertambah satu, seperti yang ditunjukkan dalam cuplikan kode berikut.
Sebagai persiapan untuk meningkatkan cakupan, tambahkan metode pengujian berikut ke class GameViewModelTest
.
@Test
fun gameViewModel_WordSkipped_ScoreUnchangedAndWordCountIncreased() {
var currentGameUiState = viewModel.uiState.value
val correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)
viewModel.updateUserGuess(correctPlayerWord)
viewModel.checkUserGuess()
currentGameUiState = viewModel.uiState.value
val lastWordCount = currentGameUiState.currentWordCount
viewModel.skipWord()
currentGameUiState = viewModel.uiState.value
// Assert that score remains unchanged after word is skipped.
assertEquals(SCORE_AFTER_FIRST_CORRECT_ANSWER, currentGameUiState.score)
// Assert that word count is increased by 1 after word is skipped.
assertEquals(lastWordCount + 1, currentGameUiState.currentWordCount)
}
Selesaikan langkah-langkah berikut untuk menjalankan ulang cakupan:
- Klik kanan file
GameViewModelTest.kt
dan dari menu, lalu pilih Run ‘GameViewModelTest' with Coverage. - Setelah build berhasil, buka elemen GameViewModel lagi dan konfirmasi bahwa persentase cakupannya adalah 100%. Laporan cakupan akhir ditampilkan dalam gambar berikut.
- Buka file
GameViewModel.kt
dan scroll ke bawah untuk memeriksa apakah jalur yang sebelumnya terlewat kini tercakup.
Anda telah mempelajari cara menjalankan, menganalisis, dan meningkatkan cakupan kode aplikasi.
Apakah persentase cakupan kode yang tinggi berarti kualitas kode aplikasi tinggi? Tidak. Cakupan kode menunjukkan persentase kode yang dicakup, atau dieksekusi, oleh pengujian unit Anda. Hal ini tidak menunjukkan bahwa kode sudah diverifikasi. Jika Anda menghapus semua pernyataan dari kode pengujian unit dan menjalankan cakupan kode, cakupan 100% tetap akan ditampilkan.
Cakupan yang tinggi tidak mengindikasikan bahwa pengujian didesain dengan benar dan pengujian memverifikasi perilaku aplikasi. Anda harus memastikan bahwa pengujian yang ditulis memiliki pernyataan yang memverifikasi perilaku class yang sedang diuji. Anda juga tidak perlu berusaha menulis pengujian unit untuk mendapatkan cakupan pengujian 100% bagi seluruh aplikasi. Sebagai gantinya, sebaiknya Anda menguji beberapa bagian kode aplikasi, seperti Aktivitas, menggunakan pengujian UI.
Namun, cakupan rendah berarti sebagian besar kode Anda benar-benar belum diuji. Gunakan cakupan kode sebagai alat untuk menemukan bagian kode yang tidak dijalankan oleh pengujian Anda, bukan alat untuk mengukur kualitas kode.
6. Mendapatkan kode solusi
Untuk mendownload kode codelab yang sudah selesai, Anda dapat menggunakan perintah git berikut:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-unscramble.git $ cd basic-android-kotlin-compose-training-unscramble $ git checkout main
Atau, Anda dapat mendownload repositori sebagai file ZIP, lalu mengekstraknya, dan membukanya di Android Studio.
Jika Anda ingin melihat kode solusi, lihat di GitHub.
7. Kesimpulan
Selamat! Anda telah mempelajari cara menentukan strategi pengujian dan menerapkan pengujian unit untuk menguji ViewModel
dan StateFlow
di aplikasi Unscramble. Saat melanjutkan membangun aplikasi Android, pastikan Anda menulis pengujian bersama fitur aplikasi untuk mengonfirmasi bahwa aplikasi Anda berfungsi dengan benar selama proses pengembangan.
Ringkasan
- Gunakan konfigurasi
testImplementation
untuk menunjukkan bahwa dependensi berlaku untuk kode sumber pengujian lokal dan bukan kode aplikasi. - Usahakan untuk mengategorikan pengujian dalam tiga skenario: Jalur berhasil, jalur error, dan kasus batas.
- Pengujian unit yang baik memiliki setidaknya empat karakteristik: pengujian yang terfokus, dapat dipahami, deterministik, dan mandiri.
- Metode pengujian dijalankan secara terpisah untuk menghindari efek samping yang tidak terduga dari status instance pengujian yang dapat diubah.
- Secara default, sebelum masing-masing metode pengujian dieksekusi, JUnit membuat instance class pengujian baru.
- Cakupan kode berperan penting untuk menentukan apakah Anda telah menguji class, metode, dan baris kode yang membentuk aplikasi Anda secara memadai.