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

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

บทนำ

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

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

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

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

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

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

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

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

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

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

สิ่งที่คุณจะทํา

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

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

แอปนี้เขียนขึ้นใน 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 ที่ได้รับความนิยม (โดยใช้เวอร์ชันสถาปัตยกรรมเชิงรับ) แอปจะทําตามสถาปัตยกรรมจากคู่มือสถาปัตยกรรมแอป โดยใช้ View Models กับ Fragments, ที่เก็บ และ Room หากคุณคุ้นเคยกับตัวอย่างด้านล่าง แอปนี้มีสถาปัตยกรรมที่คล้ายกัน

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

ด้านล่างนี้เป็นข้อมูลสรุปแพ็กเกจที่คุณจะพบ

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

.addedittask

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

.data

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

.statistics

หน้าจอสถิติ: รหัสเลเยอร์ UI สําหรับหน้าจอสถิติ

.taskdetail

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

.tasks

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

.util

ชั้นเรียนยูทิลิตี้:ชั้นเรียนที่แชร์และใช้ในส่วนต่างๆ ของแอป เช่น สําหรับเลย์เอาต์การรีเฟรชแบบปัดที่ใช้หลายหน้าจอ

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

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

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

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

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

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

การนําทางสําหรับแอปจะควบคุมโดยคอมโพเนนต์การนําทาง ดังที่ระบุไว้ในไฟล์ nav_graph.xml ระบบจะทริกเกอร์การนําทางในโมเดลข้อมูลพร็อพเพอร์ตี้โดยใช้คลาส Event นอกจากนี้ โมเดลข้อมูลพร็อพเพอร์ตี้ยังกําหนดอาร์กิวเมนต์ที่จะส่งผ่านด้วย 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. คลิกขวาที่การทดสอบและเลือกเรียกใช้ ExampleUnitTest

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

  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 สําหรับการทดสอบ (ใน JUnit 4 ของ Codelab นี้) ทั้งการยืนยันและคําอธิบายประกอบ @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

ตัวอย่างเครื่องมือการทดสอบ

@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. เปิด StatisticsUtils.kt ในซอร์สโค้ด main ใน todoapp.statistics
  2. ค้นหาฟังก์ชัน getActiveAndCompletedStats

สถิติ Utils.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 มีเครื่องมือให้คุณสร้างหลอดไฟทดสอบเพื่อช่วยให้คุณนําการทดสอบไปใช้สําหรับฟังก์ชันนี้ได้

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

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

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

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

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

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

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

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

  • หากไม่มีงานที่เสร็จแล้วและงานเดียวที่ใช้งานอยู่
  • เปอร์เซ็นต์ของการทดสอบที่ทํางานอยู่คือ 100%
  • และเปอร์เซ็นต์ของงานที่เสร็จสมบูรณ์คือ 0%
  1. เปิด StatisticsUtilsTest
  2. สร้างฟังก์ชันชื่อ getActiveAndCompletedStats_noCompleted_returnsHundredZero

สถิติ UtilsTest.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)

นี่คือรหัสที่สมบูรณ์

สถิติ UtilsTest.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: เพิ่มทรัพยากร Dependency ของ Hamcrest

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

assertEquals(result.completedTasksPercent, 0f)

// versus

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

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

  1. เปิด build.grade (Module: app) และเพิ่มทรัพยากร Dependency ต่อไปนี้

app/build.gradle

dependencies {
    // Other dependencies
    testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
}

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

  • implementation - ทรัพยากร Dependency พร้อมใช้งานในชุดแหล่งที่มาทั้งหมด ซึ่งรวมถึงชุดแหล่งที่มาของการทดสอบ
  • testImplementation - ทรัพยากร Dependency ใช้ได้เฉพาะในชุดแหล่งที่มาของการทดสอบเท่านั้น
  • androidTestImplementation - ทรัพยากร Dependency ใช้ได้เฉพาะในชุดแหล่งที่มา androidTest เท่านั้น

การกําหนดค่าที่ใช้จะเป็นตัวกําหนดตําแหน่งที่ใช้ทรัพยากร Dependency ได้ หากคุณเขียน:

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` ได้เมื่อระบบแจ้ง

การทดสอบสุดท้ายจะมีรูปแบบด้านล่างนี้

สถิติ UtilsTest.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 นอกจากนี้คุณยังจะเขียนการทดสอบโดยใช้กลยุทธ์ที่ได้จากการฝึกอบรมโปรแกรมการพัฒนาที่ขับเคลื่อนด้วยการทดสอบด้วย Test Driven Development หรือ TDD คือโรงเรียนแนวความคิดทางโปรแกรมที่จะทํางานแทนที่จะเขียนโค้ดฟีเจอร์ขึ้นมาก่อน คุณก็ต้องเขียนการทดสอบก่อน จากนั้นจึงเขียนโค้ดฟีเจอร์โดยมีเป้าหมายในการผ่านการทดสอบ

ขั้นตอนที่ 1 เขียนการทดสอบ

เขียนการทดสอบสําหรับเมื่อคุณมีรายการงานปกติ:

  1. หากมีงานที่เสร็จแล้ว 1 รายการและไม่มีงานที่ใช้งานอยู่ เปอร์เซ็นต์ activeTasks ควรเป็น 0f และเปอร์เซ็นต์งานที่เสร็จสมบูรณ์ควรเป็น 100f
  2. หากมีงานที่เสร็จสมบูรณ์ 2 รายการและงานที่ใช้งานอยู่ 3 รายการ เปอร์เซ็นต์ที่เสร็จสมบูรณ์ควรเป็น 40f และเปอร์เซ็นต์ที่ใช้งานอยู่ควรเป็น 60f

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

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

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

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

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

วิธีแก้ไข: การเขียนการทดสอบเพิ่มเติม

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

สถิติ UtilsTest.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))
    }
}

สถิติ Utils.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 ที่ไม่ได้ทดสอบสิ่งที่อยู่ในที่เก็บโดยตรง



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

TasksViewModel.kt

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

ขั้นตอนที่ 1 สร้างชั้นเรียน TasksViewModelTest

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

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

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

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

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

TasksViewModelTest.kt

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

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

  1. เพิ่มแกนการทดสอบ XX และได้รับการยกเว้น
  2. เพิ่มทรัพยากร Dependency Robolectric Testing Library
  3. เพิ่มหมายเหตุของชั้นเรียนด้วยตัวทดสอบการทดสอบ AndroidJunit4
  4. เขียนโค้ดการทดสอบ AndroidX

คุณจะต้องทําตามขั้นตอนเหล่านี้ให้เสร็จและจากนั้นเข้าใจขั้นตอนเหล่านี้

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

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

  1. เพิ่ม@RunWith(AndroidJUnit4::class)ด้านบนคลาสทดสอบ

TasksViewModelTest.kt

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

ขั้นตอนที่ 5 ใช้การทดสอบ AndroidX

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

  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 เหล่านี้สร้างขึ้นมาเพื่อทํางานทั้งในการทดสอบในพื้นที่และการทดสอบแบบมีเครื่องดนตรี ซึ่งถือเป็นเรื่องดี เนื่องจาก

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

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

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

Robolectric คืออะไร

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

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

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

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

ขั้นตอนที่ 6 แก้ไขคําเตือนเกี่ยวกับการโจรกรรม

เมื่อคุณเรียกใช้โค้ด โปรดทราบว่าระบบใช้ Robolectric

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

สรุป:

  • โดยปกติแล้ว การทดสอบโมเดลการแสดงผลอย่างเดียวอาจอยู่ในชุดแหล่งที่มา test เนื่องจากมักจะไม่ใช้โค้ดของ Android
  • คุณสามารถใช้ไลบรารี AndroidX ในการทดสอบเพื่อดูเวอร์ชันทดสอบของคอมโพเนนต์ เช่น แอปพลิเคชันและกิจกรรม
  • หากต้องการเรียกใช้โค้ด Android ที่จําลองในชุดซอร์สโค้ด test คุณสามารถเพิ่มทรัพยากร Dependency ของ 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 คือกฎหน่วย เมื่อใช้กับคําอธิบายประกอบ @get:Rule จะทําให้โค้ดบางรายการในคลาส InstantTaskExecutorRule ทํางานก่อนและหลังการทดสอบ (หากต้องการดูโค้ดแบบแม่นยํา คุณใช้แป้นพิมพ์ลัด Command+B เพื่อดูไฟล์ได้)

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

  1. เพิ่มทรัพยากร Dependency ในไลบรารีการทดสอบหลักของคอมโพเนนต์ 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 โดยทั่วไปคุณมักจะมีกิจกรรมหรือส่วนย่อย (LifecycleOwner) สังเกตเห็น LiveData

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

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

หากต้องการใช้ลักษณะการทํางาน LiveData ที่คาดไว้สําหรับโมเดลข้อมูลพร็อพเพอร์ตี้ LiveData คุณต้องสังเกต LiveData ด้วย LifecycleOwner

ปัญหานี้อาจเกิดจากการทดสอบ TasksViewModel ไม่มีกิจกรรมหรือส่วนย่อยเพื่อที่จะสังเกต 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)
    }
}

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

  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 ซึ่งจะเพิ่มผู้สังเกตการณ์ รับค่า LiveData แล้วล้างตัวสังเกต โดยพื้นฐานแล้วโค้ด observeForever เวอร์ชันที่ใช้ซ้ําได้ซึ่งระบุไว้ข้างต้น ดูคําอธิบายแบบเต็มของคลาสนี้ได้ในบล็อกโพสต์นี้

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

ในขั้นตอนนี้ ให้ใช้เมธอด getOrAwaitValue และเขียนคําสั่งยืนยันที่จะตรวจสอบว่า newTaskEvent ทํางาน

  1. รับค่า LiveData สําหรับ newTaskEvent โดยใช้ getOrAwaitValue
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
  1. ยืนยันว่าค่านั้นไม่เป็นนัล
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 สําหรับงานทั้งหมดคือ 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 โดยใช้คําสั่ง AndroidX ApplicationProvider.getApplicationContext() เดียวกัน
  • คุณเรียกใช้เมธอด setFiltering ซึ่งส่งผ่าน enum ประเภทตัวกรอง ALL_TASKS
  • คุณตรวจสอบว่า tasksAddViewVisible เป็น "จริง" โดยใช้เมธอด getOrAwaitNextValue

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

โปรดสังเกตว่าในช่วงเริ่มต้นการทดสอบทั้ง 2 รายการของคุณ คุณกําหนด TasksViewModel ไว้

TasksViewModelTest

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

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

  1. สร้างตัวแปรอินสแตนซ์ lateinit ชื่อ tasksViewModel|
  2. สร้างเมธอดชื่อ setupViewModel
  3. เพิ่มคําอธิบายประกอบด้วย @Before
  4. ย้ายรหัสอินสแตนซ์ของโมเดลข้อมูลพร็อพเพอร์ตี้ไปที่ setupViewModel

TasksViewModelTest

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }
  1. เรียกใช้โค้ดของคุณ

คำเตือน

อย่าทําสิ่งต่อไปนี้ อย่าเริ่ม

tasksViewModel

โดยมีคําจํากัดความดังต่อไปนี้

val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

ซึ่งจะทําให้ระบบใช้อินสแตนซ์เดียวกันสําหรับการทดสอบทั้งหมด นี่คือสิ่งที่คุณควรหลีกเลี่ยงเนื่องจากการทดสอบแต่ละครั้งควรมีอินสแตนซ์แรกของเรื่องใต้การทดสอบ (ViewView ซึ่งในกรณีนี้)

โค้ดสุดท้ายสําหรับ 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))
    }
    
}

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

หากต้องการดาวน์โหลดโค้ดสําหรับ Codelab ที่เสร็จสิ้นแล้ว ให้ใช้คําสั่ง 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)
  • วิธีเขียนการทดสอบหน่วยท้องถิ่นโดยใช้ JUnit และ Hamcrest
  • การตั้งค่าการทดสอบ Viewmodel ด้วยไลบรารีการทดสอบ AndroidX

หลักสูตร Udacity:

เอกสารประกอบสําหรับนักพัฒนาซอฟต์แวร์ Android

วิดีโอ:

อื่นๆ:

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