Menulis pengujian unit untuk ViewModel

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:

bb1e97c357603a27.png

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

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:

1a2dceb0dd9c618d.png

Kode awal memiliki file berikut:

  • WordsData.kt: File ini berisi daftar kata yang akan digunakan untuk pengujian dan fungsi bantuan getUnscrambledWord() 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:

  1. Buka file build.gradle.kts modul app, yang terletak di direktori app di panel Project.

bc235c0754e4e0f2.png

  1. Di dalam file, scroll ke bawah hingga Anda menemukan blok dependencies{}. Tambahkan dependensi menggunakan konfigurasi testImplementation untuk junit.
plugins {
    ...
}

android {
    ...
}

dependencies {
    ...
    testImplementation("junit:junit:4.13.2")
}
  1. Di baris notifikasi pada bagian atas file build.gradle.kts, klik Sync Now agar impor dan build selesai seperti yang ditunjukkan pada screenshot berikut:

1c20fc10750ca60c.png

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 dan isGuessedWordWrong diperbarui dengan benar.

Selesaikan langkah-langkah berikut untuk membuat pengujian:

  1. Buat paket baru com.example.android.unscramble.ui.test di set sumber pengujian dan tambahkan file seperti yang ditampilkan dalam screenshot berikut:

57d004ccc4d75833.png

f98067499852bdce.png

Untuk menulis pengujian unit class GameViewModel, Anda memerlukan instance class agar dapat memanggil metode class dan memverifikasi statusnya.

  1. Di bagian isi class GameViewModelTest, deklarasikan properti viewModel dan tetapkan instance class GameViewModel ke dalamnya.
class GameViewModelTest {
    private val viewModel = GameViewModel()
}
  1. 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()  {
    }
}
  1. 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.

  1. Di bagian isi fungsi, buat variabel currentGameUiState, dan tetapkan viewModel.uiState.value ke variabel tersebut.
@Test
fun gameViewModel_CorrectWordGuessed_ScoreUpdatedAndErrorFlagUnset() {
    var currentGameUiState = viewModel.uiState.value
}
  1. Untuk mendapatkan tebakan pemain yang benar, gunakan fungsi getUnscrambledWord(), yang menggunakan currentGameUiState.currentScrambledWord sebagai argumen dan menampilkan kata yang tidak diacak. Simpan nilai yang ditampilkan ini dalam variabel hanya baca baru bernama correctPlayerWord dan tetapkan nilai yang ditampilkan oleh fungsi getUnscrambledWord().
@Test
fun gameViewModel_CorrectWordGuessed_ScoreUpdatedAndErrorFlagUnset() {
    var currentGameUiState = viewModel.uiState.value
    val correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)

}
  1. Untuk memverifikasi apakah kata yang ditebak sudah benar, tambahkan panggilan ke metode viewModel.updateUserGuess() dan teruskan variabel correctPlayerWord sebagai argumen. Kemudian, tambahkan panggilan ke metode viewModel.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.

  1. Dapatkan instance class GameUiState dari nilai properti viewModel.uiState dan simpan dalam variabel currentGameUiState.
@Test
fun gameViewModel_CorrectWordGuessed_ScoreUpdatedAndErrorFlagUnset() {
    var currentGameUiState = viewModel.uiState.value
    val correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)
    viewModel.updateUserGuess(correctPlayerWord)
    viewModel.checkUserGuess()

    currentGameUiState = viewModel.uiState.value
}
  1. Untuk memeriksa apakah kata yang ditebak sudah benar dan skor diperbarui, gunakan fungsi assertFalse() untuk memverifikasi bahwa properti currentGameUiState.isGuessedWordWrong adalah false dan fungsi assertEquals() untuk memverifikasi bahwa nilai properti currentGameUiState.score sama dengan 20.
@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)
}
  1. Impor hal berikut:
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
  1. Agar nilai 20 dapat dibaca dan digunakan kembali, buat objek pendamping dan tetapkan 20 ke konstanta private yang bernama SCORE_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
    }
}
  1. Jalankan pengujian.

Pengujian seharusnya berhasil, karena semua pernyataan valid, seperti yang ditunjukkan pada screenshot berikut:

c412a2ac3fbefa57.png

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 ke true karena tebakannya salah.

Selesaikan langkah-langkah berikut untuk membuat pengujian:

  1. Dalam isi class GameViewModelTest, buat fungsi gameViewModel_IncorrectGuess_ErrorFlagSet() dan anotasikan dengan anotasi @Test.
@Test
fun gameViewModel_IncorrectGuess_ErrorFlagSet() {

}
  1. 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"
}
  1. Tambahkan panggilan ke metode viewModel.updateUserGuess() dan teruskan variabel incorrectPlayerWord sebagai argumen.
  2. 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()
}
  1. Tambahkan variabel currentGameUiState dan tetapkan nilai status viewModel.uiState.value ke variabel tersebut.
  2. Gunakan fungsi pernyataan untuk menyatakan bahwa nilai properti currentGameUiState.score adalah 0 dan nilai properti currentGameUiState.isGuessedWordWrong disetel ke true.
@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)
}
  1. Impor hal berikut:
import org.junit.Assert.assertTrue
  1. 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 ke 1.
  • Properti score diatur ke 0.
  • Properti isGuessedWordWrong diatur ke false.
  • Properti isGameOver diatur ke false.

Selesaikan langkah-langkah berikut untuk menambahkan pengujian:

  1. Buat metode gameViewModel_Initialization_FirstWordLoaded() dan anotasikan dengan anotasi @Test.
@Test
fun gameViewModel_Initialization_FirstWordLoaded() {

}
  1. Akses properti viewModel.uiState.value untuk mendapatkan instance awal class GameUiState. Tetapkan ke variabel hanya baca gameUiState yang baru.
@Test
fun gameViewModel_Initialization_FirstWordLoaded() {
    val gameUiState = viewModel.uiState.value
}
  1. Untuk mendapatkan kata pemain yang benar, gunakan fungsi getUnscrambledWord(), yang menggunakan kata gameUiState.currentScrambledWord dan menampilkan kata yang tidak diacak. Tetapkan nilai yang ditampilkan ke variabel hanya baca baru yang bernama unScrambledWord.
@Test
fun gameViewModel_Initialization_FirstWordLoaded() {
    val gameUiState = viewModel.uiState.value
    val unScrambledWord = getUnscrambledWord(gameUiState.currentScrambledWord)

}
  1. Untuk memverifikasi bahwa status sudah benar, tambahkan fungsi assertTrue() untuk menyatakan bahwa properti currentWordCount disetel ke 1, dan properti score disetel ke 0.
  2. Tambahkan fungsi assertFalse() untuk memverifikasi bahwa properti isGuessedWordWrong adalah false dan bahwa properti isGameOver disetel ke false.
@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)
}
  1. Impor hal berikut:
import org.junit.Assert.assertNotEquals
  1. 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 konstanta MAX_NO_OF_WORDS;
  • Properti currentGameUiState.isGameOver disetel ke true.

Selesaikan langkah-langkah berikut untuk menambahkan pengujian:

  1. Buat metode gameViewModel_AllWordsGuessed_UiStateUpdatedCorrectly() dan anotasikan dengan anotasi @Test. Dalam metode ini, buat variabel expectedScore dan tetapkan 0 ke variabel tersebut.
@Test
fun gameViewModel_AllWordsGuessed_UiStateUpdatedCorrectly() {
    var expectedScore = 0
}
  1. Untuk mendapatkan status awal, tambahkan variabel currentGameUiState, lalu tetapkan nilai properti viewModel.uiState.value ke variabel tersebut.
@Test
fun gameViewModel_AllWordsGuessed_UiStateUpdatedCorrectly() {
    var expectedScore = 0
    var currentGameUiState = viewModel.uiState.value
}
  1. Untuk mendapatkan kata pemain yang benar, gunakan fungsi getUnscrambledWord(), yang menggunakan kata currentGameUiState.currentScrambledWord dan menampilkan kata yang tidak diacak. Simpan nilai yang ditampilkan ini dalam variabel hanya baca baru bernama correctPlayerWord dan tetapkan nilai yang ditampilkan oleh fungsi getUnscrambledWord().
@Test
fun gameViewModel_AllWordsGuessed_UiStateUpdatedCorrectly() {
    var expectedScore = 0
    var currentGameUiState = viewModel.uiState.value
    var correctPlayerWord = getUnscrambledWord(currentGameUiState.currentScrambledWord)
}
  1. Untuk menguji apakah pengguna menebak semua jawaban, gunakan blok repeat untuk mengulangi eksekusi metode viewModel.updateUserGuess() dan metode viewModel.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) {

    }
}
  1. Di blok repeat, tambahkan nilai konstanta SCORE_INCREASE ke variabel expectedScore untuk menyatakan bahwa skor akan bertambah untuk setiap jawaban benar.
  2. Tambahkan panggilan ke metode viewModel.updateUserGuess() dan teruskan variabel correctPlayerWord sebagai argumen.
  3. 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()
    }
}
  1. Perbarui kata pemain saat ini, gunakan fungsi getUnscrambledWord(), yang menggunakan currentGameUiState.currentScrambledWord sebagai argumen dan menampilkan kata yang tidak diacak. Simpan nilai yang ditampilkan ini dalam variabel hanya baca baru bernama correctPlayerWord. Untuk memverifikasi bahwa statusnya sudah benar, tambahkan fungsi assertEquals() untuk memeriksa apakah nilai properti currentGameUiState.score sama dengan nilai expectedScore.
@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)
    }
}
  1. Tambahkan fungsi assertEquals() untuk menyatakan bahwa nilai properti currentGameUiState.currentWordCount sama dengan nilai konstanta MAX_NO_OF_WORDS dan bahwa nilai properti currentGameUiState.isGameOver disetel ke true.
@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)
}
  1. Impor hal berikut:
import com.example.unscramble.data.MAX_NO_OF_WORDS
  1. 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 pengujian gameViewModel_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:

  1. Klik kanan file GameViewModelTest.kt di panel project, lalu pilih cf4c5adfe69a119f.png Run 'GameViewModelTest' with Coverage.

73545d5ade3851df.png

  1. Setelah eksekusi uji selesai, klik opsi Flatten Packages di sebelah kanan panel cakupan.

90e2989f8b58d254.png

  1. Perhatikan paket com.example.android.unscramble.ui seperti yang ditunjukkan dalam gambar berikut.

1c755d17d19c6f65.png

  1. Klik dua kali nama paket com.example.android.unscramble.ui untuk menampilkan cakupan GameViewModel seperti yang ditunjukkan dalam gambar berikut:

14cf6ca3ffb557c4.png

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.

c934ba14e096bddd.png

Android Studio menampilkan file GameViewModel.kt dengan coding warna tambahan di sisi kiri jendela. Warna hijau cerah menunjukkan bahwa baris kode tersebut tercakup.

edc4e5faf352119b.png

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.

6df985f713337a0c.png

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:

  1. Klik kanan file GameViewModelTest.kt dan dari menu, lalu pilih Run ‘GameViewModelTest' with Coverage.
  2. Setelah build berhasil, buka elemen GameViewModel lagi dan konfirmasi bahwa persentase cakupannya adalah 100%. Laporan cakupan akhir ditampilkan dalam gambar berikut.

145781df2c68f71c.png

  1. Buka file GameViewModel.kt dan scroll ke bawah untuk memeriksa apakah jalur yang sebelumnya terlewat kini tercakup.

357263bdb9219779.png

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.

Pelajari lebih lanjut