มาทำ Android UI Test ด้วย Kakao กันเถอะ

Vorawut M
2 min readJul 28, 2022

--

ก่อนอื่นเลย ต้องบอกก่อนว่านี่เป็นบทความแรกของผู้เขียน หากผิดพลาดประการใดต้องขออภัยด้วยเด้ออออ

เพื่อนๆ ชาว 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 ได้ง่ายขี้น ส่วนใครที่ยังไม่เคยทำ ก็หวังว่าบทความนี้จะช่วยให้เริ่มต้นได้นะ

--

--