ก่อนอื่นเลย ต้องบอกก่อนว่านี่เป็นบทความแรกของผู้เขียน หากผิดพลาดประการใดต้องขออภัยด้วยเด้ออออ
เพื่อนๆ ชาว Android dev น่าจะคุ้นเคยกับการทำ Unit/UI Test กันดีอยู่แล้ว เพื่อให้แน่ใจว่า Business Logic ที่เราทำไปนั้น ได้ผลลัพธ์ที่ถูกต้อง ซึ่งในบทความนี้ จะ focus ที่ UI Test นะครับบ
เข้าเรื่องกันเลยดีกว่า เพื่อนๆเคยคิดไหมว่า Espresso ที่เราใช้ๆกันอยู่นี้ มีความยุ่งยาก หรือ Syntax ที่มีรูปแบบซับซ้อนมากเกินไป วันนี้ผู้เขียนมีตัวช่วยมาแนะนำซึ่งก็คือ Kakao UI Test เป็นอีกหนี่งของดีที่ทาง Agoda พัฒนาขึ้นมาให้เราใช้กันนั่นเอง
สามารถเข้าไปดูรายละเอียดได้ที่ Github
Install
dependencies {
androidTestImplementation("io.github.kakaocup:kakao:<latest version>")
}
ก่อนที่เราจะทำการ test ต้องเตรียม 2 ส่วนหลักๆ คือ
- สร้าง Screen สำหรับหน้าที่ต้องการทดสอบ ในที่นี้คือการ extend class Screen เพื่อที่จะทดสอบ views ต่างๆที่อยู่ใน Screen นั้นๆต่อไป
- สร้าง View ไว้ใน Screen เพื่อที่จะนำไปทดสอบ ซึ่งการสร้าง view นี่คือการเรียกใช้ KView โดยการอ้างอิงถึง view ที่ต้องการ
สร้าง Screen
object SomeScreen: Screen<SomeScreen>() {}
สร้าง View
object SomeScreen: Screen<SomeScreen>() {
val title = KTextView { withId(R.id.title) }
val logo = KImageView { withId(R.id.imageLogo) }
}
Kakao มีการเตรียม view พื้นฐานไว้ให้แล้ว ดังนี้
KView
KEditText
KTextView
KButton
KImageView
KWebView
KCheckbox
KViewPager
KSeekBar
KSwitch
- and more
การอ้างอิงถึง view นั้นสามารถทำได้หลายแบบ เช่น
withId
withText
withContentDescription
withDrawable
withBackgroundColor
- and more
มาเริ่มทดสอบกันเถอะ
@Test
fun displayTest() {
launchActivity<SomeActivity>().use {
SomeScreen {
title {
isDisplayed()
hasText("Kakao")
}
logo {
isDisplayed()
hasDrawable(R.drawable.kakao)
}
}
}
}
ถ้าเป็น RecyclerView ต้องทำยังไง
val recycler = KRecyclerView(
builder = { withId(R.id.recyclerView) },
itemTypeBuilder = { itemType(::Item) }
)
class Item(parent: Matcher<View>): KRecyclerItem<Item>(parent) {
val text = KTextView(parent) { withId(R.id.itemText) }
}
โดยปกติแล้ว RecyclerView นั้นสามารถมี ViewHolder ได้หลายๆแบบ Kakao เลยมี itemTypeBuilder ไว้ให้เราใส่ itemType หลายๆแบบตามแต่ที่ design ไว้เลย
จุดสังเกต KView ใน Item มีการใส่ parent เข้าไปด้วย เพราะอาจมีหลายๆ view ที่มี id เหมือนกัน
SomeScreen {
/* ... */
recycler {
firstChild<SomeScreen.Item> {
text {
isDisplayed()
hasText("Android dev")
}
}
}
}
การระบุตำแหน่งของ item ใน Adpater ทำได้ดังนี้
childAt
firstChild
lastChild
childWith
แล้ว Custom view ละ
สร้าง KCustomView ขึ้นมาเพื่อไว้ใช้ทดสอบตัว CustomView ของเรา
class KCustomView: KBaseView<KCustomView> {
val header: KTextView
val description: KTextView
constructor(function: ViewBuilder.() -> Unit) : super(function) {
header = KTextView {
withId(R.id.header)
isDescendantOfA(function)
}
description = KTextView {
withId(R.id.description)
isDescendantOfA(function)
}
}
constructor(parent: Matcher<View>, function: ViewBuilder.() -> Unit) : super(parent, function) {
header = KTextView(parent) {
withId(R.id.header)
isDescendantOfA(function)
}
description = KTextView(parent) {
withId(R.id.description)
isDescendantOfA(function)
}
}
constructor(parent: DataInteraction, function: ViewBuilder.() -> Unit) : super(parent, function) {
header = KTextView(parent) {
withId(R.id.header)
isDescendantOfA(function)
}
description = KTextView(parent) {
withId(R.id.description)
isDescendantOfA(function)
}
}
}
นำ KCustomView ไปประกาศไว้ใน Screen
val firstCustom = KCustomView { withId(R.id.firstCustom) }
val secondCustom = KCustomView { withId(R.id.secondCustom) }
เท่านี้ก็นำไปทดสอบได้แล้ววววว
firstCustom {
isDisplayed()
header { hasText("First") }
description { hasText("Test") }
}
secondCustom {
isDisplayed()
header { hasText("Second") }
description { hasText("Test") }
}
นอกจากนี้ เรายังสามารถ Custom actions และ assertions ได้อีกด้วย เพื่อให้ใช้งานกับ Custom view ของเราได้สะดวกมากยิ่งขี้น
สุดท้ายนี้หวังว่าเพื่อนๆ จะทำ UI Test ได้ง่ายขี้น ส่วนใครที่ยังไม่เคยทำ ก็หวังว่าบทความนี้จะช่วยให้เริ่มต้นได้นะ