มาสร้าง CI บน Android โดยใช้ GitHub Actions เพื่อทำ UI Test ด้วย Firebase Test Lab แบบเท่ ๆ กัน

Akexorcist
Firebase Thailand
Published in
7 min readApr 4, 2021

เพราะในโลกของการพัฒนาซอฟต์แวร์ที่ไม่ได้พัฒนาขึ้นมาจากโค้ดของคนเพียงคนเดียวนั้นมีโอกาสเกิดข้อผิดพลาดได้อยู่เสมอ รวมไปถึงการพัฒนาแอปแอนดรอยด์ก็เช่นกัน

จึงทำให้ในปัจจุบันได้มีการนำ Continuous Integration หรือ CI มาใช้ควบคู่ไปกับการเขียนเทสให้กับโค้ดในซอฟต์แวร์นั้น ๆ ด้วย เพื่อทำให้ซอฟต์แวร์สามารถทำงานได้ถูกต้องอยู่เสมอ ไม่ว่าโค้ดเหล่านั้นจะอยู่มานานเท่าไร หรือมีการเปลี่ยนคนเขียนโค้ดนั้นไปแค่ไหน

การเขียนเทสบน Android

นิยมแบ่งออกเป็น 2 ส่วนใหญ่ ๆ ด้วยกัน คือส่วนที่เป็น Unit Test สำหรับทดสอบ Logic ที่อยู่ในโค้ด และส่วนที่เป็น UI Test เพื่อทดสอบ​การแสดงผลภายในแอป

โดย Unit Test จะใช้ JVM ในการทำงาน จึงสามารถรันบนเครื่องที่ใช้เขียนโค้ดได้เลย แต่สำหรับ UI Test จะต้องใช้อุปกรณ์แอนดรอยด์ (Physical Device หรือ Emulator ก็ได้) เพื่อทำการทดสอบ

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

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

ข้อดีคือนักพัฒนาไม่ต้องเสียเวลาไปสร้าง Device Farm เพราะ Firebase Test Lab ได้เตรียมไว้ให้หมดแล้ว ไม่ว่าจะเป็นการทำ Device Matrix, การบันทึกหน้าจอระหว่างรันเทส, การแสดง Log หรือ Stack Trace, และอื่น ๆ อีกมากมาย

และ Firebase Test Lab จะมีค่าบริการในการใช้งานด้วย ซึ่งจะคิดตามระยะเวลาที่ใช้ในการทดสอบแอป โดยแยกราคาระหว่าง Physical Device กับ Virtual Device (Emulator)

การทำ Continuous Integration สำหรับ Android

ในปัจจุบันสามารถทำได้หลายวิธี ไม่ว่าจะสร้างขึ้นมาบนระบบของตัวเองอย่างการใช้ Jenkins หรือการใช้ 3rd Party Service อย่าง Travis CI หรือ Circle CI รวมไปถึง Git Repository แต่จะเจ้าที่รองรับ CI/CD ในตัว ซึ่งแต่ละแบบก็จะมีข้อดี ข้อเสีย และความเหมาะสมที่แตกต่างกันออกไป

โดยบทความนี้เลือกที่จะใช้ GitHub Actions ด้วยเหตุผลที่ว่า GitHub เป็น Git Repository ที่นักพัฒนาทั่วโลกนิยมใช้งานมากที่สุด จึงเหมาะกับนักพัฒนาทุก ๆ คน ไม่ว่าจะเป็นโปรเจคของบริษัท, โปรเจคที่เป็น Open Source, หรือโปรเจคส่วนตัวก็ตาม

โปรเจคตัวอย่างที่จะใช้ในบทความนี้

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

สามารถ Fork และ Clone เพื่อเอาไปทำตามได้เลย โดยให้ใช้ Branch ที่ชื่อ main ซึ่งเป็น Branch ที่ยังไม่ได้เพิ่ม .yml ของ GitHub Actions เข้าไปในโปรเจค

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

  • เลขคู่ : แสดงคำว่า “is even” และคำว่า “even” จะเป็นสี Indigo 400 (#5C6BC0)
  • เลขคี่ : แสดงคำว่า “is odd” และคำว่า “odd” จะเป็นสี Pink 400 (#EC407A)

โดย Unit Test จะอยู่ใน MainViewModelTest.kt และ UI Test จะอยู่ใน MainActivityUiTest.kt

สร้าง Workflow ให้กับ GitHub Actions

ในการสร้าง Workflow ให้กับ GitHub Actions จะกำหนดผ่าน YAML โดยเพิ่มไฟล์ .yml ไว้ใน <your_project>/.github/workflow/ ซึ่งผู้อ่านสามารถตั้งชื่อไฟล์ได้ตามใจชอบ

ในขั้นตอนนี้จะเพิ่มไฟล์ผ่าน Editor ของ GitHub Actions หรือใน Android Studio ก็ได้

โดยผมจะขอตั้งชื่อไฟล์ว่า android.yml

และเพื่อให้ผู้อ่านเข้าใจ Script ในแต่ละส่วนมากขึ้น ผมจะค่อย ๆ เพิ่มและอธิบาย Script ในแต่ละส่วนให้นะ

เริ่มจาก Script พื้นฐานที่จำเป็นสำหรับ Workflow

ซึ่งจะมีลักษณะแบบนี้

  • name : ชื่อของ Workflow ที่สามารถกำหนดได้ตามใจชอบ
  • on : กำหนด Trigger Event ที่จะใช้ Workflow ทำงาน โดยในตัวอย่างนี้ผมกำหนดให้ทำงานตอนที่มีการ Push หรือ Pull Request ใน Branch ที่ชื่อว่า main นั่นเอง
  • jobs : การทำงานต่าง ๆ ที่จะให้ทำใน Workflow ตัวนี้ โดยผมจะแบ่งเป็น 3 Job ด้วยกันคือ test, apk, และ firebase

Job แต่ละตัวทำหน้าที่อะไรกันบ้าง?

  • test : รัน Unit Test
  • apk : Build APK สำหรับ UI Test
  • firebase : ส่ง APK ที่ได้จาก apk ขึ้นไปรัน UI Test บน Firebase Test Lab

ดังนั้นเราจะให้ firebase ทำงานหลังจาก apk ส่วน test แยกออกมาจาก Job อื่น

โดยทุก Job จะกำหนดให้รันอยู่บน Ubuntu เวอร์ชันล่าสุด (Latest) และใช้ JDK เวอร์ชัน 1.8 ซึ่งจะต้องกำหนดไว้ใน Job แต่ละตัวอีกที

กำหนด Script ให้กับ Job สำหรับ Unit Test

ใน Job สำหรับ Unit Test จะมี Script ดังนี้

  • Checkout
  • ติดตั้ง JDK 1.8
  • กำหนดให้ gradlew เป็น Executable File
  • ใช้คำสั่ง ./gradlew test เพื่อรัน Unit Test

กำหนด Script ให้กับ Job สำหรับ Build APK เพื่อใช้ใน UI Test

การสร้างไฟล์ APK สำหรับ UI Test นั้น จะต้องสร้างไฟล์ APK ขึ้นมา 2 ไฟล์ด้วยกัน

  • APK ของแอป
  • APK สำหรับ Instrumented Test (UI Test)

โดยอุปกรณ์แอนดรอยด์จะต้องติดตั้ง APK ทั้ง 2 ไฟล์นี้ถึงจะรัน UI Test ได้

และใน Job สำหรับ Build APK เพื่อใช้ใน UI Test จะมี Script ดังนี้

  • Checkout
  • ติดตั้ง JDK 1.8
  • กำหนดให้ gradlew เป็น Executable File
  • ใช้คำสั่ง ./gradlew assembleDebug เพื่อ Build Debug APK
  • Upload Debug APK ไปเก็บไว้ใน Artifact เพื่อส่งต่อให้ Job ถัดไป โดยกำหนดชื่อเป็น app-debug
  • ใช้คำสั่ง ./gradlew assembleDebugAndroidTest เพื่อ Build Debug Instrumented APK
  • Upload Debug Instrumented APK ไปเก็บไว้ใน Artifact เพื่อส่งต่อให้ Job ถัดไป โดยกำหนดชื่อเป็น app-debug-androidTest

กำหนด Script ให้กับ Job สำหรับรัน UI Test บน Firebase Test Labs

การเรียกใช้งาน Firebase Test Lab บน GitHub Actions จะเรียกผ่าน Google Cloud ซึ่งจะต้องใช้ข้อมูลจาก Firebase อยู่ 2 อย่างด้วยกัน

  • Project ID ของ Firebase Project ที่ต้องการใช้งาน Firebase Test Lab
  • Service Key ที่เป็น Private Key จาก Firebase Project ที่ Encode ด้วย Base64

โดยค่าทั้ง 2 ตัวนี้จะไม่กำหนดไว้ใน Script โดยตรง เพราะเป็นข้อมูลสำคัญในการเข้าใช้งาน Firebase Project นั้น ๆ แต่จะเรียกผ่าน Repository Secrets ที่กำหนดไว้ใน Repository Settings บน GitHub แทน ซึ่งจะพูดถึงเรื่องนี้ทีหลัง

และใน Job สำหรับรัน UI Test บน Firebase Test Lab จะมี Script ดังนี้

  • Checkout
  • Download Debug APK ที่อยู่ใน Artifact ที่ใช้ชื่อว่า app-debug
  • Download Debug Instrumented APK ที่อยู่ใน Artifact ที่ใช้ชื่อว่า app-debug-androidTest
  • เข้าใช้งาน Google Cloud เวอร์ชัน 270.0.0 ด้วย Service Key ที่กำหนดไว้ใน Repository Secrets นั้น ๆ
  • กำหนด Project ID ของ Firebase ที่จะเรียกใช้งาน Firebase Test Lab
  • สั่งให้ Firebase Test Lab เริ่มทำงาน โดยใช้ไฟล์ APK ที่ Download มาจาก Artifact ในขั้นตอนก่อนหน้านี้

จะเห็นว่าคำสั่งรัน UI Test บน Firebase Test Lab จะต้องกำหนด --type เป็น Instrumentation ด้วย และ Path ของไฟล์ APK ทั้ง 2 ไฟล์จะถูกกำหนดไว้ใน --app และ --test โดยอุปกรณ์แอนดรอยด์ที่ใช้รัน UI Test จะเป็น Pixel 2 ที่เป็น API Level 30

อย่าเพิ่ง Push ขึ้น Repository นะ

ยังมีขั้นตอนอื่น ๆ ที่ต้องทำให้เรียบร้อยก่อน ดังนั้นอย่าเพิ่ง Push Script ชุดนี้ขึ้น Repository ไม่เช่นนั้น Workflow จะเริ่มทำงานทันที (แล้วก็ Failed)

การกำหนดอุปกรณ์แอนดรอยด์เพื่อใช้ทดสอบบน Firebase Test Lab

บน Firebase Test Lab จะสามารถกำหนดอุปกรณ์แอนดรอยด์ที่ต้องการใช้ทดสอบได้อยู่ 2 แบบด้วยกันคือ

  • กำหนดข้อมูลของเครื่องโดยตรง
  • กำหนดด้วย Device Matrix

จาก Script ก่อนหน้าจะเห็นว่าผมได้ระบุเครื่องที่ต้องการทดสอบเป็น Pixel 2 เลย ซึ่งวิธีนี้จะต้องกำหนด Model ID ของเครื่องที่ต้องการลงไปใน --device โดย Pixel 2 จะมี Model ID เป็น Pixel2

อุปกรณ์แอนดรอยด์บน Firebase Test Lab สามารถเปลี่ยนแปลงได้เสมอ

ถ้าอยากจะรู้ว่ามีอุปกรณ์แอนดรอยด์รุ่นไหนให้ใช้งานได้บ้าง จะต้องใช้คำสั่ง gcloud firebase test android models list จากเครื่องของเราเอง เพื่อแสดงรายชื่ออุปกรณ์แอนดรอยด์ทั้งหมดใน Firebase Test Lab

หรือจะดูรายชื่ออุปกรณ์แอนดรอยด์จาก Gist ของผมก็ได้นะ

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

gcloud firebase test android run
--type instrumentation
--app app-debug/app-debug.apk
--test app-debug-androidTest/app-debug-androidTest.apk
--device model=flame,version=30,locale=en,orientation=portrait
--device model=x1q,version=29,locale=en,orientation=portrait
--device model=mata,version=25,locale=en,orientation=portrait

ส่วนการกำหนดอุปกรณ์แอนดรอยด์ด้วย Device Matrix จะมีขั้นตอนที่ยุ่งยากกว่านิดหน่อย เพราะจะต้องสร้างไฟล์ YAML เอาไว้อีกชุดแทน สามารถเข้าไปดูรายละเอียดเพิ่มเติมได้ที่

เตรียม Project ID กับ Service Key ของ Firebase Project ไว้ใน Repository Secrets

อย่างที่บอกไปก่อนหน้านี้ว่า Project ID กับ Service Key ไม่ควรใส่ไว้ในโปรเจคโดยตรง แต่จะเก็บข้อมูลเหล่านี้ไว้ใน Repository Secrets ที่อยู่ใน Repository Settings บน GitHub

โดยข้อมูลทั้งสองนี้ สามารถเข้าไปดูได้ใน Project Settings ใน Firebase Project ของเราได้เลย

Project ID

จะอยู่ใน Genetal > Your project > Project ID

Service Key

จะอยู่ใน Service accounts > Firebase Admin SDK แล้วกดปุ่ม Generate new private key

เมื่อกดปุ่มดังกล่าวจะมี Popup แสดงขึ้นมาเพื่อยืนยันอีกครั้ง ให้กดปุ่ม Generate key แล้วหน้าเว็ปจะให้ดาวน์โหลดไฟล์ Private Key ในรูปของ JSON

โดยจะต้องนำข้อมูล JSON ดังกล่าวไป Encode แบบ Base64 ก่อน

ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIsCiAgInByb2plY3RfaWQiOiAiYWtleG9yY2lzdC1leHBlcmltZW50IiwKICAicHJpdmF0ZV9rZXlfaWQiOiAiZjJlMjRkYjlhNGNkODIzZmU3YjE1YjUy...

อย่างของผมจะใช้ Online Tools เพื่อความสะดวก

ใส่ข้อมูลไว้ใน Repository Secrets

เปิด Repository บน GitHub ขึ้นมา แล้วเข้าไปที่ Settings > Secrets แล้วกดปุ่ม New repository secret เพื่อกำหนดข้อมูลของ Project ID และ Service Key ลงไป โดยที่

  • Project ID : ให้กำหนดชื่อว่า FIREBASE_PROJECT_ID
  • Service Key : ให้กำหนดชื่อว่า FIREBASE_SERVICES_KEY

โดยจะเป็นชื่อที่กำหนดไว้ใน Script ของ Workflow ในตอนแรกนั่นเอง

เพิ่ม Editor Role ให้กับ Firebase Admin SDK Service Agent

เนื่องจากในตอนนี้ Service Key ที่กำหนดไว้ใน Repository Secrets จะเป็นของ Firebase Admin SDK Service Agent ที่ยังไม่มี Permission ในการเรียกใช้งาน Firebase Test Lab โดยตรง

ดังนั้นจะต้องเพิ่ม Role ที่ชื่อว่า “Editor” ให้กับ Firebase Admin SDK Service Agent ใน Google Cloud เสียก่อน

โดยเข้าไปที่ Google Cloud Console — IAM แล้วเลือก Firebase Project ที่จะใช้ Firebase Test Lab

จากนั้นให้กดปุ่ม Edit (สัญลักษณ์ดินสอ) ที่อยู่ขวามือสุดของ Member ที่ชื่อว่า “firebase-adminsdk” เพื่อแก้ไข Permission

จะมีหน้าต่าง Edit permissions แสดงขึ้นมาที่ฝั่งขวามือ ให้กดปุ่ม “ADD ANOTHER ROLE” เพื่อเพิ่ม Role ที่ชื่อว่า “Editor - Edit access to all resources” (สามารถพิมพ์ที่แถบค้นหาได้เลย) จากนั้นให้กดปุ่ม “Save” ให้เรียบร้อย

เปิดใช้งาน Cloud Tool Results API บน Google Cloud

นอกจากการเพิ่ม Role ให้กับ Firebase Admin SDK Service Agent แล้ว จะต้องเปิดใช้บริการ API ที่ชื่อว่า Cloud Tool Results API ด้วย

โดยเข้าไปที่ Google Cloud Console — Cloud Tool Results API

แล้วกดปุ่ม “Enable” เพื่อเปิดใช้งาน Cloud Tool Results API ให้กับ Firebase Project ของเรา

ในที่สุด GitHub Action ก็พร้อมทำงานแล้ว

ตอนนี้ผู้อ่านสามารถ Push Script ของ Workflow ขึ้น Repository ได้แล้ว และเมื่อ Push ขึ้นไปเรียบร้อย ให้ลองเข้าไปที่ “Actions” ของ Repository บน GitHub ก็จะเห็นว่า Workflow จะเริ่มทำงานโดยอัตโนมัติตามที่ได้กำหนดไว้ใน Script

เมื่อ Workflow ทำงานจนถึง Job ที่รัน UI Test บน Firebase Test Lab แล้ว ให้ลองเปิด Firebase Console ดู ก็จะเห็นว่ามี Test matrix แสดงขึ้นมา ซึ่งเป็น UI Test ที่มาจาก Workflow ใน GitHub Actions นั่นเอง

และหลังจากนี้เมื่อมี Push หรือ Pull Request ใด ๆ เข้าไปใน Branch นี้ก็จะทำให้ Workflow เริ่มทำงานโดยอัตโนมัติเพื่อรัน Unit Test และ UI Test ของโปรเจคเราเพื่อทดสอบว่าโค้ดทั้งหมดยังสามารถทำงานได้ถูกต้องหรือไม่

เรื่องที่ควรรู้เกี่ยวกับ Repository Secrets

ในการกำหนดค่า Project ID และ Service Key ของ Firebase Project ไว้ใน Repository Secrets เพื่อเรียกใช้งาน Firebase Test Lab จะใช้งานได้เฉพาะ Workflow Runner ท่ีมาจาก Repository นั้น ๆ เท่านั้น

นั่นหมายความว่าในกรณีที่มีคนอื่น Fork Repository ไป แล้วเปิด Pull Request กลับเข้ามา ก็จะไม่สามารถใช้งาน Firebase Test Lab ของเราได้ เพราะเป็น Workflow Runner ที่มาจาก Forked Repository จึงไม่สามารถเข้าถึง Project ID และ Service Key ที่อยู่ใน Original Repository ได้

ยินดีด้วยครับ ตอนนี้โปรเจคของคุณมี Continuous Integration ที่สร้างด้วย GitHub Actions และ Firebase Test Lab เรียบร้อยแล้ว 🔥🔥

โดยสามารถสรุปขั้นตอนทั้งหมดได้ดังนี้

  • เตรียม Script สำหรับ Workflow ของ GitHub Actions ไว้ในแอนดรอยด์
  • เตรียม Project ID และ Service Key ของ Firebase Project ไว้ใน Repository Secrets
  • เพิ่ม Editor Role ของ Firebase Admin SDK Service Agent ใน IAM บน Google Cloud Console
  • เปิดใช้งาน Cloud Tool Resuls API ใน Library บน Google Cloud Console
  • Push Script ของ Workflow ที่เตรียมไว้ในตอนแรกขึ้น Repository บน GitHub
  • รอดูผลลัพบน GitHub Actions และ Firebase Test Lab

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

แหล่งข้อมูลอ้างอิง

--

--

Akexorcist
Firebase Thailand

Lovely android developer who enjoys learning in android technology, habitual article writer about Android development for Android community in Thailand.