Codelab นี้เป็นส่วนหนึ่งของหลักสูตร Android ขั้นสูงใน Kotlin คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ แต่ไม่จำเป็นต้องทำ Codelab ของหลักสูตรทั้งหมดแสดงอยู่ในหน้า Landing Page ของ Codelab Android ขั้นสูงใน Kotlin
บทนำ
เมื่อติดตั้งใช้งานฟีเจอร์แรกของแอปแรก คุณอาจเรียกใช้โค้ดเพื่อยืนยันว่าโค้ดทํางานได้ตามที่คาดไว้ คุณได้ทำการทดสอบแล้ว แม้ว่าจะเป็นการทดสอบด้วยตนเองก็ตาม เมื่อเพิ่มและอัปเดตฟีเจอร์อย่างต่อเนื่อง คุณก็คงจะเรียกใช้โค้ดและยืนยันว่าโค้ดทำงานได้ต่อไปด้วย แต่การทำเช่นนี้ด้วยตนเองทุกครั้งนั้นเหนื่อยล้า มีแนวโน้มที่จะเกิดข้อผิดพลาด และปรับขนาดไม่ได้
คอมพิวเตอร์ปรับขนาดและทำงานอัตโนมัติได้ดี ดังนั้นนักพัฒนาแอปในบริษัททั้งขนาดเล็กและใหญ่จึงเขียนการทดสอบอัตโนมัติ ซึ่งเป็นการทดสอบที่ซอฟต์แวร์เรียกใช้และคุณไม่จำเป็นต้องใช้งานแอปด้วยตนเองเพื่อยืนยันว่าโค้ดทำงานได้
สิ่งที่คุณจะได้เรียนรู้ในชุดโค้ดแล็บนี้คือวิธีสร้างชุดการทดสอบ (หรือที่เรียกว่าชุดการทดสอบ) สำหรับแอปในโลกแห่งความเป็นจริง
Codelab แรกนี้จะครอบคลุมพื้นฐานของการทดสอบใน Android โดยคุณจะได้เขียนการทดสอบแรกและเรียนรู้วิธีทดสอบ LiveData
และ ViewModel
สิ่งที่คุณควรทราบอยู่แล้ว
คุณควรคุ้นเคยกับสิ่งต่อไปนี้
- ภาษาโปรแกรม Kotlin
- ไลบรารีหลักของ Android Jetpack ต่อไปนี้
ViewModel
และLiveData
- สถาปัตยกรรมแอปตามรูปแบบจากคู่มือสถาปัตยกรรมแอปและ Codelab หลักพื้นฐานของ Android
สิ่งที่คุณจะได้เรียนรู้
คุณจะได้เรียนรู้เกี่ยวกับหัวข้อต่อไปนี้
- วิธีเขียนและเรียกใช้การทดสอบหน่วยใน Android
- วิธีใช้การพัฒนาที่ขับเคลื่อนด้วยการทดสอบ
- วิธีเลือกการทดสอบแบบมีเครื่องมือและการทดสอบในเครื่อง
คุณจะได้เรียนรู้เกี่ยวกับไลบรารีและแนวคิดเกี่ยวกับโค้ดต่อไปนี้
สิ่งที่คุณต้องดำเนินการ
- ตั้งค่า เรียกใช้ และตีความการทดสอบทั้งในเครื่องและที่ใช้เครื่องมือใน Android
- เขียนการทดสอบหน่วยใน Android โดยใช้ JUnit4 และ Hamcrest
- เขียนการทดสอบ
LiveData
และViewModel
อย่างง่าย
ในชุด Codelab นี้ คุณจะได้ทำงานกับแอปบันทึกสิ่งที่ต้องทำ ซึ่งช่วยให้คุณเขียนงานที่ต้องทำและแสดงงานเหล่านั้นในรายการได้ จากนั้นคุณจะทำเครื่องหมายว่าเสร็จแล้วหรือไม่ กรอง หรือลบรายการก็ได้
แอปนี้เขียนด้วยภาษา Kotlin มีหลายหน้าจอ ใช้คอมโพเนนต์ Jetpack และใช้สถาปัตยกรรมจากคำแนะนำเกี่ยวกับสถาปัตยกรรมของแอป การเรียนรู้วิธีทดสอบแอปนี้จะช่วยให้คุณทดสอบแอปที่ใช้ไลบรารีและสถาปัตยกรรมเดียวกันได้
หากต้องการเริ่มต้นใช้งาน ให้ดาวน์โหลดโค้ดโดยทำดังนี้
หรือจะโคลนที่เก็บ 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 หากคุณคุ้นเคยกับตัวอย่างใดตัวอย่างหนึ่งด้านล่าง แสดงว่าแอปนี้มีสถาปัตยกรรมที่คล้ายกัน
- Codelab ของ Room with a View
- Codelab การฝึกอบรมหลักพื้นฐานของ Android Kotlin
- Codelab การฝึกอบรม Android ขั้นสูง
- ตัวอย่าง Android Sunflower
- หลักสูตรการฝึกอบรมการพัฒนาแอป Android โดยใช้ Kotlin ของ Udacity
คุณควรทำความเข้าใจสถาปัตยกรรมทั่วไปของแอปมากกว่าที่จะทำความเข้าใจตรรกะในเลเยอร์ใดเลเยอร์หนึ่งอย่างลึกซึ้ง
ต่อไปนี้คือสรุปแพ็กเกจที่คุณจะเห็น
แพ็กเกจ: | |
| หน้าจอเพิ่มหรือแก้ไขงาน: โค้ดเลเยอร์ UI สำหรับเพิ่มหรือแก้ไขงาน |
| ชั้นข้อมูล: ส่วนนี้จัดการกับชั้นข้อมูลของงาน ซึ่งมีโค้ดฐานข้อมูล เครือข่าย และที่เก็บ |
| หน้าจอสถิติ: โค้ดเลเยอร์ UI สำหรับหน้าจอสถิติ |
| หน้าจอรายละเอียดงาน: โค้ดเลเยอร์ UI สำหรับงานเดียว |
| หน้าจอ Tasks: โค้ดเลเยอร์ UI สำหรับรายการงานทั้งหมด |
| คลาสยูทิลิตี: คลาสที่ใช้ร่วมกันซึ่งใช้ในส่วนต่างๆ ของแอป เช่น สำหรับเลย์เอาต์การปัดเพื่อรีเฟรชที่ใช้ในหลายหน้าจอ |
ชั้นข้อมูล (.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
และทำการไปยังส่วนต่างๆ ระหว่างหน้าจอ
ในงานนี้ คุณจะได้ทำการทดสอบครั้งแรก
- ใน 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: เรียกใช้การทดสอบในเครื่อง
- เปิดโฟลเดอร์
test
จนกว่าจะพบไฟล์ ExampleUnitTest.kt - คลิกขวาที่ไฟล์แล้วเลือก Run ExampleUnitTest
คุณควรเห็นเอาต์พุตต่อไปนี้ในหน้าต่างเรียกใช้ที่ด้านล่างของหน้าจอ
- สังเกตเครื่องหมายถูกสีเขียวและขยายผลการทดสอบเพื่อยืนยันว่าการทดสอบ 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
- เพิ่ม
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
}
}
- ทำการทดสอบ
- ในผลการทดสอบ ให้สังเกตเครื่องหมาย X ข้างการทดสอบ
- นอกจากนี้ โปรดทราบว่า
- การยืนยันที่ไม่สำเร็จเพียงครั้งเดียวจะทำให้การทดสอบทั้งหมดไม่สำเร็จ
- ระบบจะแจ้งค่าที่คาดหวัง (3) เทียบกับค่าที่คำนวณจริง (2)
- ระบบจะนำคุณไปยังบรรทัดของการยืนยันที่ไม่สำเร็จ
(ExampleUnitTest.kt:16)
ขั้นตอนที่ 3: เรียกใช้การทดสอบที่มีการวัดคุม
การทดสอบแบบมีเครื่องควบคุมอยู่ในandroidTest
ชุดแหล่งที่มา
- เปิด
androidTest
ชุดแหล่งข้อมูล - ทำการทดสอบที่ชื่อ
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: สร้างคลาสทดสอบ
- ใน
main
ชุดแหล่งข้อมูลtodoapp.statistics
ให้เปิดStatisticsUtils.kt
- ค้นหาฟังก์ชัน
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 ของการทดสอบที่จะช่วยคุณนำการทดสอบสำหรับฟังก์ชันนี้ไปใช้
- คลิกขวาที่
getActiveAndCompletedStats
แล้วเลือกสร้าง > ทดสอบ
กล่องโต้ตอบสร้างการทดสอบจะเปิดขึ้น
- เปลี่ยนชื่อคลาส: เป็น
StatisticsUtilsTest
(แทนที่จะเป็นStatisticsUtilsKtTest
การไม่มี KT ในชื่อคลาสทดสอบจะดูดีกว่าเล็กน้อย) - เก็บค่าเริ่มต้นอื่นๆ ไว้ JUnit 4 เป็นไลบรารีการทดสอบที่เหมาะสม แพ็กเกจปลายทางถูกต้อง (เป็นสำเนาของตำแหน่งของคลาส
StatisticsUtils
) และคุณไม่จำเป็นต้องเลือกช่องทำเครื่องหมายใดๆ (การดำเนินการนี้จะสร้างโค้ดเพิ่มเติม แต่คุณจะเขียนการทดสอบตั้งแต่ต้น) - กดตกลง
กล่องโต้ตอบเลือกไดเรกทอรีปลายทางจะเปิดขึ้น
คุณจะทำการทดสอบในเครื่องเนื่องจากฟังก์ชันของคุณทำการคำนวณทางคณิตศาสตร์และจะไม่มีโค้ดเฉพาะของ Android ดังนั้นจึงไม่จำเป็นต้องเรียกใช้ในอุปกรณ์จริงหรืออุปกรณ์จำลอง
- เลือกไดเรกทอรี
test
(ไม่ใช่androidTest
) เนื่องจากคุณจะเขียนการทดสอบในเครื่อง - คลิกตกลง
- สังเกต
StatisticsUtilsTest
คลาสที่สร้างขึ้นในtest/statistics/
ขั้นตอนที่ 2: เขียนฟังก์ชันทดสอบแรก
คุณจะเขียนการทดสอบที่ตรวจสอบสิ่งต่อไปนี้
- หากไม่มีงานที่เสร็จสมบูรณ์และมีงานที่ใช้งานอยู่ 1 งาน
- เปอร์เซ็นต์ของการทดสอบที่ใช้งานอยู่คือ 100%
- และเปอร์เซ็นต์ของงานที่เสร็จสมบูรณ์คือ 0%
- เปิด
StatisticsUtilsTest
- สร้างฟังก์ชันชื่อ
getActiveAndCompletedStats_noCompleted_returnsHundredZero
StatisticsUtilsTest.kt
class StatisticsUtilsTest {
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
// Create an active task
// Call your function
// Check the result
}
}
- เพิ่มคำอธิบายประกอบ
@Test
เหนือชื่อฟังก์ชันเพื่อระบุว่าฟังก์ชันนั้นเป็นการทดสอบ - สร้างรายการงาน
// Create an active task
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
- โทรหา
getActiveAndCompletedStats
พร้อมระบุงานเหล่านี้
// Call your function
val result = getActiveAndCompletedStats(tasks)
- ตรวจสอบว่า
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)
}
}
- เรียกใช้การทดสอบ (คลิกขวาที่
StatisticsUtilsTest
แล้วเลือกเรียกใช้)
โดยควรมีลักษณะดังนี้
ขั้นตอนที่ 3: เพิ่มการอ้างอิง Hamcrest
เนื่องจากการทดสอบทำหน้าที่เป็นเอกสารประกอบของสิ่งที่โค้ดทำ จึงเป็นเรื่องดีที่การทดสอบจะอยู่ในรูปแบบที่มนุษย์อ่านได้ เปรียบเทียบข้อความยืนยัน 2 ข้อต่อไปนี้
assertEquals(result.completedTasksPercent, 0f)
// versus
assertThat(result.completedTasksPercent, `is`(0f))
ส่วนการยืนยันที่ 2 อ่านแล้วดูเหมือนประโยคที่มนุษย์พูดมากกว่า โดยเขียนขึ้นโดยใช้เฟรมเวิร์กการยืนยันที่เรียกว่า Hamcrest อีกเครื่องมือที่ดีสำหรับการเขียนการยืนยันที่อ่านได้คือไลบรารี Truth คุณจะใช้ Hamcrest ใน Codelab นี้เพื่อเขียนการยืนยัน
- เปิด
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 เพื่อเขียนการยืนยัน
- อัปเดตการทดสอบ
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))
}
}
- เรียกใช้การทดสอบที่อัปเดตแล้วเพื่อยืนยันว่ายังใช้งานได้
Codelab นี้ไม่ได้สอนรายละเอียดทั้งหมดของ Hamcrest ดังนั้นหากต้องการดูข้อมูลเพิ่มเติม โปรดดูบทแนะนำอย่างเป็นทางการ
นี่คืองานที่ไม่บังคับสำหรับฝึกฝน
ในงานนี้ คุณจะได้เขียนการทดสอบเพิ่มเติมโดยใช้ JUnit และ Hamcrest นอกจากนี้ คุณยังจะได้เขียนการทดสอบโดยใช้กลยุทธ์ที่ได้จากแนวทางปฏิบัติของโปรแกรมการพัฒนาที่ขับเคลื่อนด้วยการทดสอบ การพัฒนาแบบทดสอบขับเคลื่อนหรือ TDD เป็นแนวคิดการเขียนโปรแกรมที่บอกว่าแทนที่จะเขียนโค้ดฟีเจอร์ก่อน คุณควรเขียนการทดสอบก่อน จากนั้นคุณจะเขียนโค้ดฟีเจอร์โดยมีเป้าหมายคือการผ่านการทดสอบ
ขั้นตอนที่ 1 เขียนการทดสอบ
เขียนการทดสอบเมื่อคุณมีรายการงานปกติ ดังนี้
- หากมีงานที่เสร็จสมบูรณ์ 1 งานและไม่มีงานที่ใช้งานอยู่
activeTasks
เปอร์เซ็นต์ควรเป็น0f
และเปอร์เซ็นต์งานที่เสร็จสมบูรณ์ควรเป็น100f
- หากมีงานที่เสร็จสมบูรณ์แล้ว 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()
)
}
คุณจะใช้การพัฒนาที่ขับเคลื่อนด้วยการทดสอบเพื่อแก้ไขโค้ดและเขียนการทดสอบ การพัฒนาที่ขับเคลื่อนด้วยการทดสอบจะทำตามขั้นตอนต่อไปนี้
- เขียนการทดสอบโดยใช้โครงสร้าง Given, When, Then และมีชื่อตามรูปแบบ
- ยืนยันว่าการทดสอบไม่สำเร็จ
- เขียนโค้ดขั้นต่ำเพื่อให้การทดสอบผ่าน
- ทำซ้ำสำหรับการทดสอบทั้งหมด
แทนที่จะเริ่มด้วยการแก้ไขข้อบกพร่อง คุณจะเริ่มด้วยการเขียนการทดสอบก่อน จากนั้นคุณจะยืนยันได้ว่าคุณมีเทสต์ที่ช่วยป้องกันไม่ให้เกิดข้อบกพร่องเหล่านี้อีกในอนาคต
- หากมีรายการว่าง (
emptyList()
) เปอร์เซ็นต์ทั้ง 2 รายการควรเป็น 0f - หากเกิดข้อผิดพลาดในการโหลดงาน รายการจะเป็น
null
และเปอร์เซ็นต์ทั้ง 2 ควรเป็น 0 - เรียกใช้การทดสอบและยืนยันว่าการทดสอบล้มเหลว
ขั้นตอนที่ 3 แก้ไขข้อบกพร่อง
เมื่อมีชุดการทดสอบแล้ว ให้แก้ไขข้อบกพร่อง
- แก้ไขข้อบกพร่องใน
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
)
}
}
- เรียกใช้การทดสอบอีกครั้งและยืนยันว่าการทดสอบทั้งหมดผ่านแล้ว
การทำตาม 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
- เปิดชั้นเรียนที่ต้องการทดสอบใน
tasks
แพ็กเกจTasksViewModel.
- ในโค้ด ให้คลิกขวาที่ชื่อคลาส
TasksViewModel
-> สร้าง -> ทดสอบ
- ในหน้าจอสร้างการทดสอบ ให้คลิกตกลงเพื่อยอมรับ (ไม่ต้องเปลี่ยนการตั้งค่าเริ่มต้นใดๆ)
- ในกล่องโต้ตอบเลือกไดเรกทอรีปลายทาง ให้เลือกไดเรกทอรีtest
ขั้นตอนที่ 2 เริ่มเขียนการทดสอบ ViewModel
ในขั้นตอนนี้ คุณจะเพิ่มการทดสอบ ViewModel เพื่อทดสอบว่าเมื่อเรียกใช้เมธอด addNewTask
ระบบจะเรียกใช้ Event
สำหรับเปิดหน้าต่างงานใหม่
- สร้างการทดสอบใหม่ชื่อ
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 อย่างถูกต้อง
- เพิ่มทรัพยากร Dependency หลักและส่วนขยายของ AndroidX Test
- เพิ่มการอ้างอิง Robolectric Testing library
- ใส่คำอธิบายประกอบในคลาสด้วยตัวดำเนินการทดสอบ AndroidJunit4
- เขียนโค้ดการทดสอบ AndroidX
คุณจะทำตามขั้นตอนเหล่านี้ให้เสร็จสมบูรณ์และจากนั้นทำความเข้าใจว่าขั้นตอนเหล่านี้ทำงานร่วมกันอย่างไร
ขั้นตอนที่ 3 เพิ่มการอ้างอิง Gradle
- คัดลอกทรัพยากร 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
- เพิ่ม
@RunWith(AndroidJUnit4::class)
เหนือชั้นเรียนทดสอบ
TasksViewModelTest.kt
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Test code
}
ขั้นตอนที่ 5 ใช้ AndroidX Test
ตอนนี้คุณใช้ไลบรารีการทดสอบ AndroidX ได้แล้ว ซึ่งรวมถึงเมธอด ApplicationProvider.getApplicationContex
t
ซึ่งจะรับ Application Context
- สร้าง
TasksViewModel
โดยใช้ApplicationProvider.getApplicationContext()
จากไลบรารีการทดสอบ AndroidX
TasksViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
- โทรหา
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
}
- เรียกใช้การทดสอบเพื่อยืนยันว่าใช้งานได้
แนวคิด: 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
- เพิ่มบรรทัดต่อไปนี้ลงในไฟล์ 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 สิ่งต่อไปนี้
- ใช้
InstantTaskExecutorRule
- ตรวจสอบ
LiveData
การสังเกตการณ์
ขั้นตอนที่ 1 ใช้ InstantTaskExecutorRule
InstantTaskExecutorRule
คือ JUnit Rule เมื่อใช้กับคำอธิบายประกอบ @get:Rule
จะทำให้โค้ดบางส่วนในคลาส InstantTaskExecutorRule
ทำงานก่อนและหลังการทดสอบ (หากต้องการดูโค้ดที่แน่นอน ให้ใช้แป้นพิมพ์ลัด Command+B เพื่อดูไฟล์)
กฎนี้จะเรียกใช้ Background Job ทั้งหมดที่เกี่ยวข้องกับ Architecture Components ในเธรดเดียวกันเพื่อให้ผลการทดสอบเกิดขึ้นพร้อมกันและเป็นไปตามลำดับที่ทำซ้ำได้ เมื่อเขียนการทดสอบที่มีการทดสอบ LiveData ให้ใช้กฎนี้
- เพิ่มการอ้างอิง Gradle สำหรับไลบรารีการทดสอบหลักของ Architecture Components (ซึ่งมีกฎนี้)
app/build.gradle
testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
- เปิด
TasksViewModelTest.kt
- เพิ่ม
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
เพื่อ
- ทริกเกอร์เหตุการณ์
onChanged
- ทริกเกอร์การเปลี่ยนรูปแบบ
หากต้องการให้ 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 ง่ายขึ้น
- สร้างไฟล์ Kotlin ใหม่ชื่อ
LiveDataTestUtil.kt
ในชุดแหล่งที่มาtest
- คัดลอกและวางโค้ดด้านล่าง
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
แล้ว
- รับค่า
LiveData
สำหรับnewTaskEvent
โดยใช้getOrAwaitValue
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
- ยืนยันว่าค่าไม่ใช่ค่า 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()))
}
}
- เรียกใช้โค้ดและดูการทดสอบที่ผ่าน
ตอนนี้คุณได้เห็นวิธีเขียนการทดสอบแล้ว ลองเขียนด้วยตัวเอง ในขั้นตอนนี้ ให้ใช้ทักษะที่คุณได้เรียนรู้มาฝึกเขียนTasksViewModel
อีกรายการ
ขั้นตอนที่ 1 เขียนการทดสอบ ViewModel ของคุณเอง
คุณจะเขียน setFilterAllTasks_tasksAddViewVisible()
การทดสอบนี้ควรตรวจสอบว่าหากคุณตั้งค่าประเภทตัวกรองให้แสดงงานทั้งหมด ปุ่มเพิ่มงานจะปรากฏให้เห็น
- ใช้
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.
จะควบคุมระดับการมองเห็นของปุ่มเพื่อเพิ่มงาน
- ทำการทดสอบ
ขั้นตอนที่ 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
- สร้างตัวแปรอินสแตนซ์
lateinit
ชื่อtasksViewModel|
- สร้างเมธอดชื่อ
setupViewModel
- ใส่คำอธิบายประกอบด้วย
@Before
- ย้ายโค้ดการเริ่มต้น ViewModel ไปยัง
setupViewModel
TasksViewModelTest
// Subject under test
private lateinit var tasksViewModel: TasksViewModel
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
}
- เรียกใช้โค้ด
คำเตือน
อย่า ทำสิ่งต่อไปนี้ อย่าเริ่มต้น
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 ก็ได้
Codelab นี้ครอบคลุมหัวข้อต่อไปนี้
- วิธีเรียกใช้การทดสอบจาก Android Studio
- ความแตกต่างระหว่างการทดสอบในเครื่อง (
test
) กับการทดสอบเครื่องมือ (androidTest
) - วิธีเขียนการทดสอบ 1 หน่วยในเครื่องโดยใช้ JUnit และ Hamcrest
- การตั้งค่าการทดสอบ ViewModel ด้วยไลบรารีการทดสอบ AndroidX
หลักสูตร Udacity:
เอกสารประกอบสำหรับนักพัฒนาแอป Android
- คู่มือสถาปัตยกรรมแอป
- JUnit4
- Hamcrest
- ไลบรารีการทดสอบ Robolectric
- คลังทดสอบ AndroidX
- ไลบรารีการทดสอบหลักของคอมโพเนนต์สถาปัตยกรรม AndroidX
- ชุดแหล่งที่มา
- ทดสอบจากบรรทัดคำสั่ง
วิดีโอ:
อื่นๆ:
ดูลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ได้ที่หน้า Landing Page ของ Codelab Android ขั้นสูงใน Kotlin