ข้อมูลเบื้องต้นเกี่ยวกับการทดสอบ

Codelab นี้เป็นส่วนหนึ่งของหลักสูตร Android ขั้นสูงใน Kotlin คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ แต่ไม่จำเป็นต้องทำ Codelab ของหลักสูตรทั้งหมดแสดงอยู่ในหน้า Landing Page ของ Codelab Android ขั้นสูงใน Kotlin

บทนำ

เมื่อติดตั้งใช้งานฟีเจอร์แรกของแอปแรก คุณอาจเรียกใช้โค้ดเพื่อยืนยันว่าโค้ดทํางานได้ตามที่คาดไว้ คุณได้ทำการทดสอบแล้ว แม้ว่าจะเป็นการทดสอบด้วยตนเองก็ตาม เมื่อเพิ่มและอัปเดตฟีเจอร์อย่างต่อเนื่อง คุณก็คงจะเรียกใช้โค้ดและยืนยันว่าโค้ดทำงานได้ต่อไปด้วย แต่การทำเช่นนี้ด้วยตนเองทุกครั้งนั้นเหนื่อยล้า มีแนวโน้มที่จะเกิดข้อผิดพลาด และปรับขนาดไม่ได้

คอมพิวเตอร์ปรับขนาดและทำงานอัตโนมัติได้ดี ดังนั้นนักพัฒนาแอปในบริษัททั้งขนาดเล็กและใหญ่จึงเขียนการทดสอบอัตโนมัติ ซึ่งเป็นการทดสอบที่ซอฟต์แวร์เรียกใช้และคุณไม่จำเป็นต้องใช้งานแอปด้วยตนเองเพื่อยืนยันว่าโค้ดทำงานได้

สิ่งที่คุณจะได้เรียนรู้ในชุดโค้ดแล็บนี้คือวิธีสร้างชุดการทดสอบ (หรือที่เรียกว่าชุดการทดสอบ) สำหรับแอปในโลกแห่งความเป็นจริง

Codelab แรกนี้จะครอบคลุมพื้นฐานของการทดสอบใน Android โดยคุณจะได้เขียนการทดสอบแรกและเรียนรู้วิธีทดสอบ LiveData และ ViewModel

สิ่งที่คุณควรทราบอยู่แล้ว

คุณควรคุ้นเคยกับสิ่งต่อไปนี้

สิ่งที่คุณจะได้เรียนรู้

คุณจะได้เรียนรู้เกี่ยวกับหัวข้อต่อไปนี้

  • วิธีเขียนและเรียกใช้การทดสอบหน่วยใน Android
  • วิธีใช้การพัฒนาที่ขับเคลื่อนด้วยการทดสอบ
  • วิธีเลือกการทดสอบแบบมีเครื่องมือและการทดสอบในเครื่อง

คุณจะได้เรียนรู้เกี่ยวกับไลบรารีและแนวคิดเกี่ยวกับโค้ดต่อไปนี้

สิ่งที่คุณต้องดำเนินการ

  • ตั้งค่า เรียกใช้ และตีความการทดสอบทั้งในเครื่องและที่ใช้เครื่องมือใน Android
  • เขียนการทดสอบหน่วยใน Android โดยใช้ JUnit4 และ Hamcrest
  • เขียนการทดสอบ LiveData และ ViewModel อย่างง่าย

ในชุด Codelab นี้ คุณจะได้ทำงานกับแอปบันทึกสิ่งที่ต้องทำ ซึ่งช่วยให้คุณเขียนงานที่ต้องทำและแสดงงานเหล่านั้นในรายการได้ จากนั้นคุณจะทำเครื่องหมายว่าเสร็จแล้วหรือไม่ กรอง หรือลบรายการก็ได้

แอปนี้เขียนด้วยภาษา Kotlin มีหลายหน้าจอ ใช้คอมโพเนนต์ Jetpack และใช้สถาปัตยกรรมจากคำแนะนำเกี่ยวกับสถาปัตยกรรมของแอป การเรียนรู้วิธีทดสอบแอปนี้จะช่วยให้คุณทดสอบแอปที่ใช้ไลบรารีและสถาปัตยกรรมเดียวกันได้

หากต้องการเริ่มต้นใช้งาน ให้ดาวน์โหลดโค้ดโดยทำดังนี้

ดาวน์โหลด Zip

หรือจะโคลนที่เก็บ Github สำหรับโค้ดก็ได้

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

ในงานนี้ คุณจะได้เรียกใช้แอปและสำรวจโค้ดเบส

ขั้นตอนที่ 1: เรียกใช้แอปตัวอย่าง

เมื่อดาวน์โหลดแอป TO-DO แล้ว ให้เปิดแอปใน Android Studio แล้วเรียกใช้ ซึ่งควรจะคอมไพล์ได้ สำรวจแอปโดยทำดังนี้

  • สร้างงานใหม่ด้วยปุ่มการทำงานแบบลอยที่มีเครื่องหมายบวก ป้อนชื่อก่อน แล้วป้อนข้อมูลเพิ่มเติมเกี่ยวกับงาน บันทึกด้วย FAB เครื่องหมายถูกสีเขียว
  • ในรายการงาน ให้คลิกชื่องานที่คุณเพิ่งทำเสร็จ แล้วดูหน้าจอรายละเอียดของงานนั้นเพื่อดูคำอธิบายที่เหลือ
  • ในรายการหรือบนหน้าจอรายละเอียด ให้เลือกช่องทำเครื่องหมายของงานนั้นเพื่อตั้งค่าสถานะเป็นเสร็จสมบูรณ์
  • กลับไปที่หน้าจองาน เปิดเมนูตัวกรอง แล้วกรองงานตามสถานะใช้งานอยู่และเสร็จสมบูรณ์
  • เปิดลิ้นชักการนำทางแล้วคลิกสถิติ
  • กลับไปที่หน้าจอภาพรวม แล้วเลือกล้างสิ่งที่เสร็จแล้วจากเมนูลิ้นชักการนำทางเพื่อลบงานทั้งหมดที่มีสถานะเสร็จแล้ว

ขั้นตอนที่ 2: สำรวจโค้ดแอปตัวอย่าง

แอปสิ่งที่ต้องทำสร้างขึ้นจากตัวอย่างการทดสอบและสถาปัตยกรรมของ Architecture Blueprints ยอดนิยม (ใช้ตัวอย่างเวอร์ชันสถาปัตยกรรมแบบรีแอ็กทีฟ) แอปใช้สถาปัตยกรรมจากคู่มือสถาปัตยกรรมแอป โดยใช้ ViewModel กับ Fragment, ที่เก็บ และ Room หากคุณคุ้นเคยกับตัวอย่างใดตัวอย่างหนึ่งด้านล่าง แสดงว่าแอปนี้มีสถาปัตยกรรมที่คล้ายกัน

คุณควรทำความเข้าใจสถาปัตยกรรมทั่วไปของแอปมากกว่าที่จะทำความเข้าใจตรรกะในเลเยอร์ใดเลเยอร์หนึ่งอย่างลึกซึ้ง

ต่อไปนี้คือสรุปแพ็กเกจที่คุณจะเห็น

แพ็กเกจ: com.example.android.architecture.blueprints.todoapp

.addedittask

หน้าจอเพิ่มหรือแก้ไขงาน: โค้ดเลเยอร์ UI สำหรับเพิ่มหรือแก้ไขงาน

.data

ชั้นข้อมูล: ส่วนนี้จัดการกับชั้นข้อมูลของงาน ซึ่งมีโค้ดฐานข้อมูล เครือข่าย และที่เก็บ

.statistics

หน้าจอสถิติ: โค้ดเลเยอร์ UI สำหรับหน้าจอสถิติ

.taskdetail

หน้าจอรายละเอียดงาน: โค้ดเลเยอร์ UI สำหรับงานเดียว

.tasks

หน้าจอ Tasks: โค้ดเลเยอร์ UI สำหรับรายการงานทั้งหมด

.util

คลาสยูทิลิตี: คลาสที่ใช้ร่วมกันซึ่งใช้ในส่วนต่างๆ ของแอป เช่น สำหรับเลย์เอาต์การปัดเพื่อรีเฟรชที่ใช้ในหลายหน้าจอ

ชั้นข้อมูล (.data)

แอปนี้มีเลเยอร์เครือข่ายจำลองในแพ็กเกจ remote และเลเยอร์ฐานข้อมูลในแพ็กเกจ local เพื่อความสะดวก ในโปรเจ็กต์นี้เราจะจำลองเลเยอร์เครือข่ายด้วย HashMap ที่มีความล่าช้าแทนที่จะสร้างคำขอเครือข่ายจริง

DefaultTasksRepository จะประสานงานหรือเป็นตัวกลางระหว่างเลเยอร์เครือข่ายกับเลเยอร์ฐานข้อมูล และเป็นสิ่งที่ส่งคืนข้อมูลไปยังเลเยอร์ UI

เลเยอร์ UI ( .addedittask, .statistics, .taskdetail, .tasks)

แพ็กเกจเลเยอร์ UI แต่ละแพ็กเกจจะมี Fragment และ ViewModel พร้อมกับคลาสอื่นๆ ที่จำเป็นสำหรับ UI (เช่น อะแดปเตอร์สำหรับรายการงาน) TaskActivity คือกิจกรรมที่มี Fragment ทั้งหมด

การไปยังส่วนต่างๆ

คอมโพเนนต์การนำทางจะควบคุมการนำทางของแอป โดยกำหนดไว้ในไฟล์ nav_graph.xml การนำทางจะทริกเกอร์ใน ViewModel โดยใช้คลาส Event และ ViewModel จะกำหนดอาร์กิวเมนต์ที่จะส่งด้วย Fragment จะสังเกต Event และทำการไปยังส่วนต่างๆ ระหว่างหน้าจอ

ในงานนี้ คุณจะได้ทำการทดสอบครั้งแรก

  1. ใน Android Studio ให้เปิดแผงโปรเจ็กต์ แล้วค้นหาโฟลเดอร์ 3 โฟลเดอร์ต่อไปนี้
  • com.example.android.architecture.blueprints.todoapp
  • com.example.android.architecture.blueprints.todoapp (androidTest)
  • com.example.android.architecture.blueprints.todoapp (test)

โฟลเดอร์เหล่านี้เรียกว่าชุดแหล่งที่มา ชุดแหล่งที่มาคือโฟลเดอร์ที่มีซอร์สโค้ดสำหรับแอปของคุณ ชุดแหล่งที่มาซึ่งมีสีเขียว (androidTest และ test) จะมีการทดสอบของคุณ เมื่อสร้างโปรเจ็กต์ Android ใหม่ คุณจะได้รับชุดแหล่งที่มา 3 ชุดต่อไปนี้โดยค่าเริ่มต้น ดังนี้

  • main: มีโค้ดแอป โค้ดนี้จะแชร์ในแอปเวอร์ชันต่างๆ ทั้งหมดที่คุณสร้างได้ (เรียกว่าตัวแปรบิลด์)
  • androidTest: มีการทดสอบที่เรียกว่าการทดสอบแบบมีเครื่องควบคุม
  • test: มีการทดสอบที่เรียกว่าการทดสอบในพื้นที่

ความแตกต่างระหว่างการทดสอบในเครื่องและการทดสอบที่มีการตรวจสอบอยู่ที่วิธีการเรียกใช้

การทดสอบในเครื่อง (test ชุดแหล่งข้อมูล)

การทดสอบเหล่านี้จะทำงานใน JVM ของเครื่องพัฒนาภายในเครื่อง และไม่จำเป็นต้องใช้โปรแกรมจำลองหรืออุปกรณ์จริง ด้วยเหตุนี้ ตัวละครจึงวิ่งเร็ว แต่มีความสมจริงน้อยลง ซึ่งหมายความว่าตัวละครจะแสดงท่าทางไม่เหมือนกับที่ตัวละครจะทำในโลกแห่งความเป็นจริง

ในการทดสอบในเครื่องของ Android Studio จะแสดงด้วยไอคอนสามเหลี่ยมสีเขียวและสีแดง

การทดสอบที่มีการตรวจสอบ (androidTest ชุดแหล่งที่มา)

การทดสอบเหล่านี้ทำงานบนอุปกรณ์ Android จริงหรืออุปกรณ์ที่จำลองขึ้น จึงแสดงให้เห็นสิ่งที่เกิดขึ้นในโลกแห่งความเป็นจริง แต่ก็ช้ากว่ามากเช่นกัน

ใน Android Studio การทดสอบที่มีการตรวจสอบจะแสดงด้วยหุ่นยนต์ Android ที่มีไอคอนสามเหลี่ยมสีเขียวและสีแดง

ขั้นตอนที่ 1: เรียกใช้การทดสอบในเครื่อง

  1. เปิดโฟลเดอร์ test จนกว่าจะพบไฟล์ ExampleUnitTest.kt
  2. คลิกขวาที่ไฟล์แล้วเลือก Run ExampleUnitTest

คุณควรเห็นเอาต์พุตต่อไปนี้ในหน้าต่างเรียกใช้ที่ด้านล่างของหน้าจอ

  1. สังเกตเครื่องหมายถูกสีเขียวและขยายผลการทดสอบเพื่อยืนยันว่าการทดสอบ 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. เพิ่ม assertEquals(3, 1 + 1) ลงในการทดสอบ addition_isCorrect

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

StatisticsUtils.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 คือคลาสข้อมูลที่มีตัวเลข 2 ตัว ได้แก่ เปอร์เซ็นต์ของงานที่เสร็จสมบูรณ์ และเปอร์เซ็นต์ของงานที่ใช้งานอยู่

Android Studio มีเครื่องมือในการสร้าง Stub ของการทดสอบที่จะช่วยคุณนำการทดสอบสำหรับฟังก์ชันนี้ไปใช้

  1. คลิกขวาที่ getActiveAndCompletedStats แล้วเลือกสร้าง > ทดสอบ

กล่องโต้ตอบสร้างการทดสอบจะเปิดขึ้น

  1. เปลี่ยนชื่อคลาส: เป็น StatisticsUtilsTest (แทนที่จะเป็น StatisticsUtilsKtTest การไม่มี KT ในชื่อคลาสทดสอบจะดูดีกว่าเล็กน้อย)
  2. เก็บค่าเริ่มต้นอื่นๆ ไว้ JUnit 4 เป็นไลบรารีการทดสอบที่เหมาะสม แพ็กเกจปลายทางถูกต้อง (เป็นสำเนาของตำแหน่งของคลาส StatisticsUtils) และคุณไม่จำเป็นต้องเลือกช่องทำเครื่องหมายใดๆ (การดำเนินการนี้จะสร้างโค้ดเพิ่มเติม แต่คุณจะเขียนการทดสอบตั้งแต่ต้น)
  3. กดตกลง

กล่องโต้ตอบเลือกไดเรกทอรีปลายทางจะเปิดขึ้น

คุณจะทำการทดสอบในเครื่องเนื่องจากฟังก์ชันของคุณทำการคำนวณทางคณิตศาสตร์และจะไม่มีโค้ดเฉพาะของ Android ดังนั้นจึงไม่จำเป็นต้องเรียกใช้ในอุปกรณ์จริงหรืออุปกรณ์จำลอง

  1. เลือกไดเรกทอรี test (ไม่ใช่ androidTest) เนื่องจากคุณจะเขียนการทดสอบในเครื่อง
  2. คลิกตกลง
  3. สังเกตStatisticsUtilsTest คลาสที่สร้างขึ้นใน test/statistics/

ขั้นตอนที่ 2: เขียนฟังก์ชันทดสอบแรก

คุณจะเขียนการทดสอบที่ตรวจสอบสิ่งต่อไปนี้

  • หากไม่มีงานที่เสร็จสมบูรณ์และมีงานที่ใช้งานอยู่ 1 งาน
  • เปอร์เซ็นต์ของการทดสอบที่ใช้งานอยู่คือ 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 แล้วเลือกเรียกใช้)

โดยควรมีลักษณะดังนี้

ขั้นตอนที่ 3: เพิ่มการอ้างอิง Hamcrest

เนื่องจากการทดสอบทำหน้าที่เป็นเอกสารประกอบของสิ่งที่โค้ดทำ จึงเป็นเรื่องดีที่การทดสอบจะอยู่ในรูปแบบที่มนุษย์อ่านได้ เปรียบเทียบข้อความยืนยัน 2 ข้อต่อไปนี้

assertEquals(result.completedTasksPercent, 0f)

// versus

assertThat(result.completedTasksPercent, `is`(0f))

ส่วนการยืนยันที่ 2 อ่านแล้วดูเหมือนประโยคที่มนุษย์พูดมากกว่า โดยเขียนขึ้นโดยใช้เฟรมเวิร์กการยืนยันที่เรียกว่า Hamcrest อีกเครื่องมือที่ดีสำหรับการเขียนการยืนยันที่อ่านได้คือไลบรารี Truth คุณจะใช้ Hamcrest ใน Codelab นี้เพื่อเขียนการยืนยัน

  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. อัปเดตการทดสอบ getActiveAndCompletedStats_noCompleted_returnsHundredZero() เพื่อใช้ assertThat ของ Hamcrest แทน assertEquals
// 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. หากมีงานที่เสร็จสมบูรณ์ 1 งานและไม่มีงานที่ใช้งานอยู่ activeTasks เปอร์เซ็นต์ควรเป็น 0f และเปอร์เซ็นต์งานที่เสร็จสมบูรณ์ควรเป็น 100f
  2. หากมีงานที่เสร็จสมบูรณ์แล้ว 2 งานและงานที่ใช้งานอยู่ 3 งาน เปอร์เซ็นต์งานที่เสร็จสมบูรณ์แล้วควรเป็น 40f และเปอร์เซ็นต์งานที่ใช้งานอยู่ควรเป็น 60f

ขั้นตอนที่ 2 เขียนการทดสอบสำหรับข้อบกพร่อง

โค้ดสำหรับ getActiveAndCompletedStats ตามที่เขียนมีข้อบกพร่อง สังเกตว่าฟังก์ชันนี้จัดการอย่างไม่ถูกต้องในกรณีที่ลิสต์ว่างเปล่าหรือเป็นค่า Null ในทั้ง 2 กรณีนี้ เปอร์เซ็นต์ทั้ง 2 ควรเป็น 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. เขียนการทดสอบโดยใช้โครงสร้าง Given, When, Then และมีชื่อตามรูปแบบ
  2. ยืนยันว่าการทดสอบไม่สำเร็จ
  3. เขียนโค้ดขั้นต่ำเพื่อให้การทดสอบผ่าน
  4. ทำซ้ำสำหรับการทดสอบทั้งหมด

แทนที่จะเริ่มด้วยการแก้ไขข้อบกพร่อง คุณจะเริ่มด้วยการเขียนการทดสอบก่อน จากนั้นคุณจะยืนยันได้ว่าคุณมีเทสต์ที่ช่วยป้องกันไม่ให้เกิดข้อบกพร่องเหล่านี้อีกในอนาคต

  1. หากมีรายการว่าง (emptyList()) เปอร์เซ็นต์ทั้ง 2 รายการควรเป็น 0f
  2. หากเกิดข้อผิดพลาดในการโหลดงาน รายการจะเป็น null และเปอร์เซ็นต์ทั้ง 2 ควรเป็น 0
  3. เรียกใช้การทดสอบและยืนยันว่าการทดสอบล้มเหลว

ขั้นตอนที่ 3 แก้ไขข้อบกพร่อง

เมื่อมีชุดการทดสอบแล้ว ให้แก้ไขข้อบกพร่อง

  1. แก้ไขข้อบกพร่องใน getActiveAndCompletedStats โดยแสดงผล 0f หาก tasks เป็น null หรือว่างเปล่า
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))
    }
}

StatisticsUtils.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
        )
    }
}

คุณทำได้ดีมากในการเขียนและเรียกใช้การทดสอบขั้นพื้นฐาน จากนั้นคุณจะได้เรียนรู้วิธีเขียนการทดสอบ ViewModel และ LiveData ขั้นพื้นฐาน

ใน Codelab ส่วนที่เหลือ คุณจะได้เรียนรู้วิธีเขียนการทดสอบสำหรับคลาส Android 2 คลาสที่ใช้กันทั่วไปในแอปส่วนใหญ่ ได้แก่ ViewModel และ LiveData

คุณเริ่มต้นด้วยการเขียนการทดสอบสำหรับ TasksViewModel


คุณจะมุ่งเน้นไปที่การทดสอบที่มีตรรกะทั้งหมดใน ViewModel และไม่ต้องอาศัยโค้ดที่เก็บ โค้ดที่เก็บเกี่ยวข้องกับโค้ดแบบอะซิงโครนัส ฐานข้อมูล และการเรียกเครือข่าย ซึ่งทั้งหมดนี้เพิ่มความซับซ้อนในการทดสอบ ตอนนี้คุณจะหลีกเลี่ยงการดำเนินการดังกล่าวและมุ่งเน้นที่การเขียนการทดสอบฟังก์ชันการทำงานของ ViewModel ที่ไม่ได้ทดสอบสิ่งใดในที่เก็บโดยตรง



การทดสอบที่คุณจะเขียนจะตรวจสอบว่าเมื่อคุณเรียกใช้เมธอด addNewTask ระบบจะเรียกใช้ Event สำหรับการเปิดหน้าต่างงานใหม่ นี่คือโค้ดแอปที่คุณจะทดสอบ

TasksViewModel.kt

fun addNewTask() {
   _newTaskEvent.value = Event(Unit)
}

ขั้นตอนที่ 1 สร้างคลาส TasksViewModelTest

ในขั้นตอนนี้ คุณจะสร้างไฟล์ทดสอบสำหรับ TasksViewModelTest โดยทำตามขั้นตอนเดียวกันกับที่ทำสำหรับ StatisticsUtilTest

  1. เปิดชั้นเรียนที่ต้องการทดสอบในtasksแพ็กเกจTasksViewModel.
  2. ในโค้ด ให้คลิกขวาที่ชื่อคลาส TasksViewModel -> สร้าง -> ทดสอบ

  1. ในหน้าจอสร้างการทดสอบ ให้คลิกตกลงเพื่อยอมรับ (ไม่ต้องเปลี่ยนการตั้งค่าเริ่มต้นใดๆ)
  2. ในกล่องโต้ตอบเลือกไดเรกทอรีปลายทาง ให้เลือกไดเรกทอรีtest

ขั้นตอนที่ 2 เริ่มเขียนการทดสอบ ViewModel

ในขั้นตอนนี้ คุณจะเพิ่มการทดสอบ 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 เพื่อทดสอบ ตัวสร้างของอินสแตนซ์นั้นต้องมี Application Context แต่ในการทดสอบนี้ คุณไม่ได้สร้างแอปพลิเคชันแบบเต็มที่มีกิจกรรม, UI และ Fragment ดังนั้นคุณจะรับบริบทของแอปพลิเคชันได้อย่างไร

TasksViewModelTest.kt

// Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(???)

ไลบรารีการทดสอบ AndroidX มีคลาสและเมธอดที่ให้เวอร์ชันของคอมโพเนนต์ เช่น แอปพลิเคชันและกิจกรรมที่ออกแบบมาสำหรับการทดสอบ เมื่อคุณมีการทดสอบในเครื่องที่ต้องใช้คลาสเฟรมเวิร์ก Android จำลอง(เช่น Context ของแอปพลิเคชัน) ให้ทำตามขั้นตอนต่อไปนี้เพื่อตั้งค่า AndroidX Test อย่างถูกต้อง

  1. เพิ่มทรัพยากร Dependency หลักและส่วนขยายของ AndroidX Test
  2. เพิ่มการอ้างอิง Robolectric Testing library
  3. ใส่คำอธิบายประกอบในคลาสด้วยตัวดำเนินการทดสอบ AndroidJunit4
  4. เขียนโค้ดการทดสอบ AndroidX

คุณจะทำตามขั้นตอนเหล่านี้ให้เสร็จสมบูรณ์และจากนั้นทำความเข้าใจว่าขั้นตอนเหล่านี้ทำงานร่วมกันอย่างไร

ขั้นตอนที่ 3 เพิ่มการอ้างอิง Gradle

  1. คัดลอกทรัพยากร Dependency เหล่านี้ลงในไฟล์ build.gradle ของโมดูลแอปเพื่อเพิ่มทรัพยากร Dependency หลักของ AndroidX Test Core และ Ext รวมถึงทรัพยากร Dependency การทดสอบ 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 Test Runner

  1. เพิ่ม @RunWith(AndroidJUnit4::class) เหนือชั้นเรียนทดสอบ

TasksViewModelTest.kt

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
    // Test code
}

ขั้นตอนที่ 5 ใช้ AndroidX Test

ตอนนี้คุณใช้ไลบรารีการทดสอบ AndroidX ได้แล้ว ซึ่งรวมถึงเมธอด ApplicationProvider.getApplicationContext ซึ่งจะรับ Application Context

  1. สร้าง TasksViewModel โดยใช้ ApplicationProvider.getApplicationContext() จากไลบรารีการทดสอบ AndroidX

TasksViewModelTest.kt

// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
  1. โทรหา addNewTask ที่หมายเลข tasksViewModel

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 Test ทำงานอย่างไร

AndroidX Test คืออะไร

AndroidX Test คือชุดไลบรารีสำหรับการทดสอบ ซึ่งรวมถึงคลาสและเมธอดที่ให้เวอร์ชันของคอมโพเนนต์ เช่น แอปพลิเคชันและกิจกรรม ซึ่งมีไว้สำหรับการทดสอบ ตัวอย่างเช่น โค้ดที่คุณเขียนนี้เป็นตัวอย่างของฟังก์ชันการทดสอบ AndroidX สำหรับการรับบริบทของแอปพลิเคชัน

ApplicationProvider.getApplicationContext()

ข้อดีอย่างหนึ่งของ AndroidX Test API คือสร้างขึ้นมาให้ทำงานได้ทั้งกับการทดสอบในเครื่องและการทดสอบที่มีการตรวจสอบ ซึ่งเป็นเรื่องดีเนื่องจาก

  • คุณสามารถทำการทดสอบเดียวกันเป็นการทดสอบในเครื่องหรือการทดสอบที่มีการวัดประสิทธิภาพ
  • คุณไม่จำเป็นต้องเรียนรู้ API การทดสอบที่แตกต่างกันสำหรับการทดสอบในเครื่องกับการทดสอบที่มีการวัด

ตัวอย่างเช่น เนื่องจากคุณเขียนโค้ดโดยใช้ไลบรารีการทดสอบ AndroidX คุณจึงย้ายคลาส TasksViewModelTest จากโฟลเดอร์ test ไปยังโฟลเดอร์ androidTest ได้ และการทดสอบจะยังคงทำงานต่อไป getApplicationContext() จะทำงานแตกต่างกันเล็กน้อยขึ้นอยู่กับว่าเป็นการทดสอบในเครื่องหรือการทดสอบที่มีการวัดผล

  • หากเป็นการทดสอบที่มีการวัดผล จะได้รับ Context ของแอปพลิเคชันจริงที่ระบุเมื่อบูตโปรแกรมจำลองหรือเชื่อมต่อกับอุปกรณ์จริง
  • หากเป็นการทดสอบในเครื่อง ระบบจะใช้สภาพแวดล้อม Android จำลอง

Robolectric คืออะไร

Robolectric เป็นผู้จัดหาสภาพแวดล้อม Android จำลองที่ AndroidX Test ใช้สำหรับการทดสอบในเครื่อง Robolectric เป็นไลบรารีที่สร้างสภาพแวดล้อม Android จำลองสำหรับการทดสอบและทำงานได้เร็วกว่าการบูตโปรแกรมจำลองหรือการเรียกใช้ในอุปกรณ์ หากไม่มีการขึ้นต่อกันของ Robolectric คุณจะได้รับข้อผิดพลาดนี้

@RunWith(AndroidJUnit4::class) ทำอะไร

Test Runner คือคอมโพเนนต์ JUnit ที่เรียกใช้การทดสอบ หากไม่มีโปรแกรมเรียกใช้การทดสอบ การทดสอบจะทำงานไม่ได้ JUnit มีโปรแกรมเรียกใช้การทดสอบเริ่มต้นที่คุณจะได้รับโดยอัตโนมัติ @RunWith จะแทนที่เครื่องมือเรียกใช้การทดสอบเริ่มต้นนั้น

โปรแกรมเรียกใช้การทดสอบ AndroidJUnit4 ช่วยให้ AndroidX Test เรียกใช้การทดสอบได้แตกต่างกันไป ขึ้นอยู่กับการทดสอบว่าเป็นการทดสอบที่ใช้เครื่องมือหรือการทดสอบในเครื่อง

ขั้นตอนที่ 6 แก้ไขคำเตือน Robolectric

เมื่อเรียกใช้โค้ด คุณจะเห็นว่ามีการใช้ Robolectric

เนื่องจาก AndroidX Test และโปรแกรมเรียกใช้การทดสอบ AndroidJunit4 คุณจึงทำได้โดยไม่ต้องเขียนโค้ด Robolectric แม้แต่บรรทัดเดียวโดยตรง

คุณอาจเห็นคำเตือน 2 รายการ

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

คุณแก้ไขNo such manifest file: ./AndroidManifest.xmlคำเตือนได้โดยการอัปเดตไฟล์ Gradle

  1. เพิ่มบรรทัดต่อไปนี้ลงในไฟล์ Gradle เพื่อให้ระบบใช้ไฟล์ Manifest ของ Android ที่ถูกต้อง ตัวเลือก includeAndroidResources ช่วยให้คุณเข้าถึงทรัพยากร Android ในการทดสอบหน่วยได้ รวมถึงไฟล์ AndroidManifest

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 ต้องใช้ Java 9 สำหรับ Codelab นี้ ให้ตั้งค่า SDK เป้าหมายและ SDK ที่คอมไพล์ไว้ที่ 28 แทนที่จะพยายามกำหนดค่า Android Studio ให้ใช้ Java 9

โดยสรุป

  • โดยปกติแล้ว การทดสอบโมเดล Pure View จะอยู่ในชุดแหล่งที่มาของ test เนื่องจากโค้ดมักจะไม่ต้องใช้ Android
  • คุณสามารถใช้ไลบรารีการทดสอบ AndroidX เพื่อรับเวอร์ชันทดสอบของคอมโพเนนต์ต่างๆ เช่น แอปพลิเคชันและกิจกรรม
  • หากต้องการเรียกใช้โค้ด Android จำลองในtestชุดแหล่งที่มา คุณสามารถเพิ่มการอ้างอิง 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 เราขอแนะนำให้คุณทำ 2 สิ่งต่อไปนี้

  1. ใช้ InstantTaskExecutorRule
  2. ตรวจสอบLiveDataการสังเกตการณ์

ขั้นตอนที่ 1 ใช้ InstantTaskExecutorRule

InstantTaskExecutorRule คือ JUnit Rule เมื่อใช้กับคำอธิบายประกอบ @get:Rule จะทำให้โค้ดบางส่วนในคลาส InstantTaskExecutorRule ทำงานก่อนและหลังการทดสอบ (หากต้องการดูโค้ดที่แน่นอน ให้ใช้แป้นพิมพ์ลัด Command+B เพื่อดูไฟล์)

กฎนี้จะเรียกใช้ Background Job ทั้งหมดที่เกี่ยวข้องกับ Architecture Components ในเธรดเดียวกันเพื่อให้ผลการทดสอบเกิดขึ้นพร้อมกันและเป็นไปตามลำดับที่ทำซ้ำได้ เมื่อเขียนการทดสอบที่มีการทดสอบ LiveData ให้ใช้กฎนี้

  1. เพิ่มการอ้างอิง Gradle สำหรับไลบรารีการทดสอบหลักของ Architecture Components (ซึ่งมีกฎนี้)

app/build.gradle

testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
  1. เปิด TasksViewModelTest.kt
  2. เพิ่ม InstantTaskExecutorRule ภายในคลาส TasksViewModelTest

TasksViewModelTest.kt

class TasksViewModelTest {
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()
    
    // Other code...
}

ขั้นตอนที่ 2 เพิ่มคลาส LiveDataTestUtil.kt

ขั้นตอนถัดไปคือการตรวจสอบว่าLiveDataที่คุณกำลังทดสอบได้รับการสังเกต

เมื่อใช้ LiveData คุณมักจะมีกิจกรรมหรือ Fragment (LifecycleOwner) ที่สังเกต LiveData

viewModel.resultLiveData.observe(fragment, Observer {
    // Observer code here
})

การสังเกตนี้มีความสำคัญ คุณต้องมีผู้สังเกตการณ์ที่ใช้งานอยู่บน LiveData เพื่อ

หากต้องการให้ LiveData ของโมเดลมุมมองมีLiveDataตามที่คาดไว้ คุณต้องสังเกตLiveDataด้วย LifecycleOwner

ซึ่งทำให้เกิดปัญหาคือ ในการทดสอบ TasksViewModel คุณไม่มีกิจกรรมหรือ Fragment ที่จะสังเกต LiveData หากต้องการหลีกเลี่ยงปัญหานี้ คุณสามารถใช้วิธี observeForever ซึ่งจะช่วยให้LiveDataได้รับการสังเกตการณ์อย่างต่อเนื่องโดยไม่ต้องมีLifecycleOwner เมื่อคุณ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 เพื่อให้การเพิ่ม Observer ง่ายขึ้น

  1. สร้างไฟล์ Kotlin ใหม่ชื่อ LiveDataTestUtil.kt ในชุดแหล่งที่มา test


  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
}

วิธีนี้ค่อนข้างซับซ้อน โดยจะสร้างฟังก์ชันส่วนขยาย Kotlin ที่ชื่อ getOrAwaitValue ซึ่งจะเพิ่ม Observer รับค่า LiveData แล้วล้างข้อมูล Observer ซึ่งโดยพื้นฐานแล้วเป็นโค้ด observeForever เวอร์ชันสั้นที่นำกลับมาใช้ใหม่ได้ตามที่แสดงไว้ด้านบน ดูคำอธิบายแบบเต็มของคลาสนี้ได้ในบล็อกโพสต์นี้

ขั้นตอนที่ 3 ใช้ getOrAwaitValue เพื่อเขียนการยืนยัน

ในขั้นตอนนี้ คุณจะใช้วิธี getOrAwaitValue และเขียนคำสั่งยืนยันที่ตรวจสอบว่าทริกเกอร์ newTaskEvent แล้ว

  1. รับค่า LiveData สำหรับ newTaskEvent โดยใช้ getOrAwaitValue
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 enum สำหรับงานทั้งหมดคือ 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))
    }

ตรวจสอบว่าคุณได้ทำสิ่งต่อไปนี้หรือไม่

  • คุณสร้าง tasksViewModel โดยใช้คำสั่ง ApplicationProvider.getApplicationContext() AndroidX เดียวกัน
  • คุณเรียกใช้เมธอด setFiltering โดยส่งผ่านการแจงนับประเภทตัวกรอง ALL_TASKS
  • คุณตรวจสอบว่า tasksAddViewVisible เป็นจริงโดยใช้วิธี getOrAwaitNextValue

ขั้นตอนที่ 3 เพิ่มกฎ @Before

สังเกตว่าเมื่อเริ่มต้นการทดสอบทั้ง 2 รายการ คุณจะกำหนดTasksViewModel

TasksViewModelTest

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

เมื่อมีโค้ดการตั้งค่าที่ซ้ำกันสำหรับการทดสอบหลายครั้ง คุณสามารถใช้คำอธิบายประกอบ @Before เพื่อสร้างวิธีการตั้งค่าและนำโค้ดที่ซ้ำกันออกได้ เนื่องจากการทดสอบทั้งหมดนี้จะทดสอบ TasksViewModel และต้องใช้ ViewModel ให้ย้ายโค้ดนี้ไปยังบล็อก @Before

  1. สร้างตัวแปรอินสแตนซ์ lateinit ชื่อ tasksViewModel|
  2. สร้างเมธอดชื่อ setupViewModel
  3. ใส่คำอธิบายประกอบด้วย @Before
  4. ย้ายโค้ดการเริ่มต้น ViewModel ไปยัง 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))
    }
    
}

คลิกที่นี่เพื่อดูความแตกต่างระหว่างโค้ดที่คุณเริ่มต้นกับโค้ดสุดท้าย

หากต้องการดาวน์โหลดโค้ดสำหรับโค้ดแล็บที่เสร็จสมบูรณ์แล้ว คุณสามารถใช้คำสั่ง Git ด้านล่าง

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


หรือคุณจะดาวน์โหลดที่เก็บเป็นไฟล์ Zip, แตกไฟล์ และเปิดใน Android Studio ก็ได้

ดาวน์โหลด Zip

Codelab นี้ครอบคลุมหัวข้อต่อไปนี้

  • วิธีเรียกใช้การทดสอบจาก Android Studio
  • ความแตกต่างระหว่างการทดสอบในเครื่อง (test) กับการทดสอบเครื่องมือ (androidTest)
  • วิธีเขียนการทดสอบ 1 หน่วยในเครื่องโดยใช้ JUnit และ Hamcrest
  • การตั้งค่าการทดสอบ ViewModel ด้วยไลบรารีการทดสอบ AndroidX

หลักสูตร Udacity:

เอกสารประกอบสำหรับนักพัฒนาแอป Android

วิดีโอ:

อื่นๆ:

ดูลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ได้ที่หน้า Landing Page ของ Codelab Android ขั้นสูงใน Kotlin