How to Test Flutter Apps ทดสอบแอปพลิเคชันที่สร้างจาก Flutter อย่างไร?

Wittaya Sudthinitaed
Muze Innovation
Published in
5 min readJun 11, 2024

วันนี้เพื่อน ๆ ห้ามพลาดเด็ดขาด! เพราะ Muze Innovation จะมาแนะนำวิธีการทดสอบแอปที่ผลิตโดย Flutter แต่ Flutter คืออะไรกันนะ มีข้อดีข้อเสียอย่างไร และจะมีการเทสเคสแบบไหนให้ดูบ้าง รอติดตามจาก พี่ดิว-ณัฐฤทธิ์ Software Developer ของ Muze Innovation ที่จะมาอธิบายเรื่องราวของ Flutter ให้ฟังกันแบบชัด ๆ ทีละขั้นตอนได้เลย รับรองว่าหลังอ่านจบเพื่อน ๆ จะเข้าใจและรู้ถึงวิธีการทำงานของ Flutter มากขึ้นแน่นอน ถ้างั้นไม่รอช้า ไปอ่านกันได้เลยยย!

มาเริ่มทำความรู้จัก Flutter โดยให้พี่ดิวอธิบายอย่างเข้าใจง่ายให้ฟังกันเลยดีกว่า Flutter คือ framework ของการพัฒนาแอปพลิเคชัน ที่สามารถพัฒนาได้ทั้ง iOS และ Android ในภาษาเดียวกัน หรือก็คือทำครั้งเดียวแล้วได้ 2 แอปพลิเคชันในทันที

ซึ่งข้อดีคือ เราจะสามารถพัฒนาแอปได้อย่างรวดเร็วมากขึ้น โดยเขียนครั้งเดียวแล้วได้ 2 แอปพลิเคชัน แทนที่เราจะต้องมาเขียนภาษา Kotlin ที่เป็น Android แยก แล้วก็เขียน Swift แล้วจะได้ iOS แยก ซึ่งจะลดเวลาการ develop ได้เพิ่มขึ้นเยอะมาก ส่วนข้อเสียคือ library ที่ลึก ๆ อย่างเช่นพวก software ของ native ลึก ๆ flutter จะทำได้ไม่ดีเท่าการเขียน native โดยตรงครับ

ต่อมาเดี๋ยวเรามาดูภาพกว้าง ๆ กันว่า Flutter Testing นั้นสามารถเทสอะไรบ้าง โดยจะยกตัวอย่างเป็น Test Pyramid ให้ทุกคนได้เห็นภาพและเข้าใจได้ง่าย ๆ ซึ่งในการ Test Software จะแบ่งออกเป็นพีระมิดตามนี้ จะมีฐานล่างสุดเป็น Unit Tests ไล่ขึ้นมาเป็น Widget Tests, Integration Tests แล้วก็ E2E Tests ซึ่งแต่ละส่วนมีหน้าที่อะไรบ้าง ไปดูกันเลย

- Unit Tests จะเป็นการเทสในระดับฟังก์ชันของการทำงาน คือเทสการทำงานในหน่วยที่เล็กที่สุด เห็นได้ไวที่สุดว่าตรงไหนมีการทำงานผิดพลาด

- Widget Tests ใน flutter เราจะใช้ library ที่ชื่อว่า Golden เทสขึ้นมาเพื่อที่จะทำให้ UI ของเราที่แก้ไขไปนั้นมั่นใจได้ว่าไม่มีการเปลี่ยนแปลง

- Integration Tests จะเป็นการเทสในระดับของเซอร์วิส เราจะรวมฟังก์ชันเล็ก ๆ มาต่อด้วยกันกับเซอร์วิสต่าง ๆ เช่น ถ้ามีเน็ตเวิร์คก็จะเทสตั้งแต่ฟังก์ชันต่อกับเน็ตเวิร์ค แล้วดูว่าทำงานได้ถูกต้องหรือเปล่า

- E2E จะเป็นการเทสเพื่อเช็กว่าทุก ๆ component หรือ system ย่อย ๆ ของเราทำงานร่วมกันได้ดีหรือเปล่า โดยสิ่งที่โฟกัสก็คือเราจะโฟกัส customer experience หรือ customer journey ในชีวิตจริงเป็นหลัก

ภาพรวมของ Test Pyramid และหน้าที่หลัก ๆ ของแต่ละระบบก็จะประมาณนี้ ส่วนในฝั่งของ developer ของ flutter ที่พี่ดิวจะมาเล่าการเทสวันนี้ให้ทุกคนได้ฟัง จะมีสองส่วนก็คือ Unit Tests กับ Widget Tests ครับ

เริ่มที่ Flutter unit testing เราจะเทสในระดับ method ย่อย ๆ unit ย่อย ๆ เราจะไม่เห็น UI เพราะว่าเราเทสในระดับฟังก์ชัน ซึ่ง library ที่ใช้ก็จะใช้เป็น flutter test เวลาสร้างโปรเจกต์ขึ้นมาก็จะมี library นี้เป็นพื้นฐานให้อยู่แล้ว โดยที่เราไม่ต้องลงเพิ่มอะไร

ยกตัวอย่างให้เห็นภาพ โดยสร้างแอปพลิเคชัน Counter demo ขึ้นมา โดยด้านบนสุดก็จะเป็น number คือเลข 5 แล้วก็มีปุ่ม + แล้วก็ปุ่ม — คือการบวก 1 ครั้งก็จะเป็นการบวกทีละ 5 ตาม number ที่อยู่ด้านบน แล้วก็การลบก็คือลบทีละ 5 ตาม number ที่อยู่ด้านบน โดยเลข 5 นี้ จะจำลองเป็นการ get จาก API ขึ้นมา

ในส่วนของโมเดล ถ้ามองเป็นโค้ดก็จะแบ่งเป็น Class ตามนี้ โดยจะมี Class Counter ขึ้นมา แล้วก็จะมีตัวแปรที่ชื่อว่า number แสดงที่เป็นเลข 5 แล้วก็ result แสดงในส่วนของ result ที่แสดงเวลากด + แล้วเห็นเป็นตัวเลข จะแสดงตามนี้ Class Counter นี้จะเรียกว่าเป็น Class สำหรับจัดการ Model

ในการเทสมาดูฟังก์ชันของโค้ดกันต่อ ซึ่งแบ่งออกเป็นในส่วนของ Build, Increment แล้วก็ Decrement ในส่วนของ Build คือเวลาเราเข้าหน้าแอปเข้ามาครั้งแรก ฟังก์ชัน Build จะถูกรันแล้วก็จะแสดงเห็นเป็นหน้า UI ตอนเริ่มต้นก่อนที่จะกดปุ่ม

คราวนี้พอกดปุ่มก็จะมารันฟังก์ชัน ถ้าปุ่ม + ก็จะรันฟังก์ชัน Increment ก็จะเป็นการเอา number มาบวกกับ result ก็จะได้เป็นผลลัพธ์ที่แสดงบนหน้าจอ ส่วน Decrement ก็จะเป็นการเอา result ลบกับ number ก็จะรีเทิร์นออกเป็น result บนหน้าจอ

หลัก ๆ ก็จะมี 3 ส่วนตามนี้ สรุป Build ก็คือเป็นสเตทแรกสุดที่เราเข้ามาในหน้า Screen ส่วน Increment คือฟังก์ชันของการกดปุ่ม + Decrement ก็คือฟังก์ชันของการกดปุ่ม -

ทีนี้ในส่วนของการเทส เราจะเทสฟังก์ชัน Build อะไรบ้าง เราก็จะเอา Mock มาใส่ใน Container แล้วถ้ามีการเรียกฟังก์ชัน getNumber เราจะรีเทิร์นอะไรออกไป เราก็จะมาเขียนใส่จากนั้นเราก็จะรันคำสั่ง await.build เพื่อที่จะรันคำสั่งฟังก์ชัน Build ว่าจะเกิดอะไรขึ้น แล้วเราก็จะตั้ง matcherResult ออกมาเพื่อที่จะ expect ว่า result ที่เราต้องการจะมี number เท่ากับ 5 แล้ว result เท่ากับ 0 จากนั้นเราก็เอา notifier.number มาเช็ก matcher.number ว่ามีค่าเท่ากันหรือเปล่า result เท่ากันกับที่เรา expect หรือเปล่า แล้วค่อย make sure ว่า repository นี้มีการถูก called getNumber 2 ครั้งจริงไหม

ซึ่งทั้งหมดนี้ พี่ดิวสรุปให้เข้าใจง่ายก็คือ อันดับแรกเราจะต้อง set up ก่อน จากนั้นก็ mock ค่าที่ต้องการไว้ จากนั้นรันคำสั่งหรือรันฟังก์ชันที่เราต้องการเทสออกมา แล้วก็สร้างตัวแปรที่เราต้องการ expect ทิ้งไว้ แล้วรันคำสั่ง expect หรือ verify เพื่อเช็กความถูกต้องของการทำงานครับ

เมื่อเราเขียนเทสครบทั้งสามส่วนแล้ว เราจะรันเทสยังไง ก็ให้ไปที่ terminal แล้วก็รัน flutter test สิ่งที่ได้ก็คือ เราจะได้ result ออกมาว่าผลเทสนั้นผ่านหรือเปล่า อันนี้ยกตัวอย่างในกรณีที่เราเทส initial state, increment state, decrement state แล้วเทสผ่านก็จะขึ้นเป็นสีเขียว ที่กล่าวไปคือการเทสเรื่อง provider

ต่อมาเราจะเทสในส่วนของ Repository ซึ่งเลเยอร์นี้มีแค่ฟังก์ชันเดียวก็คือ Get number อันดับแรกเราก็ set up ก่อนเลย แล้วก็มาสร้างฟังก์ชัน getNumber แล้วก็ expect result ว่าจะได้ตามที่เราต้องการหรือเปล่า ซึ่งการรันเทสก็รันแบบเดียวกัน ถ้าสมมติว่าเรารันเทส failed จะโชว์เป็นกากบาทสีแดงแล้วก็ตามด้วยฟังก์ชัน ถ้าคลิกเข้าไปดูก็จะบอกว่าเรา expected 8 แต่ว่าเราวัดได้ 5 แสดงว่ามีบางอย่างผิดพลาด เราก็แค่ไปแก้ฟังก์ชันให้ถูกต้อง หรือไปแก้เทสให้ถูกต้อง unit test ก็จะประมาณนี้ครับ

ในส่วนที่สองจะเป็น Widget Tests หรือก็คือ การเทส UI ซึ่งเราจะใช้ Golden test ใน flutter โดยขอยกตัวอย่าง หน้าตา UI อันเดิมจากของ unit test นะครับ แล้วเราก็จะ add library ที่ชื่อว่า golden toolkit เข้ามา เสร็จแล้วในส่วนของการเขียนโค้ด เราจะเขียนฟังก์ชันของการเทส screen test success ว่าต้องการให้หน้าจอแสดงผลยังไง ถ้า test success

แล้วก็ขอเล่าการทำงานคร่าว ๆ อันดับแรกเราก็จะโหลด app font ขึ้นมาก่อน เพื่อให้ฟอนต์ตรงตามที่แอปพลิเคชันเราใช้ จากนั้นเราก็จะต้องสร้าง mock ที่ไม่เกี่ยวข้องขึ้นมา แล้วสร้าง scenario device ที่จะทดสอบ อันนี้ยกตัวอย่างเราจะทดสอบที่ device iphone11 จากนั้นเราก็ add scenario widget เข้ามา พอ create แล้วจะให้ทำอะไร เช่น เราจะให้ระบบ changeState โชว์ number เท่ากับ 5 แล้วก็ result เท่ากับ 5

จากนั้นเราก็จะสั่ง builder โดยที่จะรันที่ device iphone11 จากนั้นหลังจาก pumpDeviceBuilder ได้แล้ว ก็จะเอาไฟล์มาเซฟที่ ‘my_home_page_test_success’ ในแต่ละ state ในกรณี success state เราจะ render แบบนี้ ถ้า error state เราจะ render แบบนี้ เราก็ต้องมาเขียนฟังก์ชันแยกย่อยของ scenario error state อีก ก็จะเป็นประมาณนี้ครับ หลัก ๆ เขียนเหมือนเดิมเลยแต่มาเปลี่ยนตรงที่ changeState ให้มัน error เป็นต้น

ต่อมาก่อนที่เราจะรันเทสได้ เราจะต้องทำการสร้างแม่พิมพ์เข้ามาก่อน ซึ่งจะต้องรันคำสั่ง flutter test แล้วตามด้วยชื่อไฟล์ที่เราเขียนแล้วก็อัปเดต golden เข้าไป สิ่งที่ได้ก็คือเราจะได้รูปภาพที่เราทำเป็นแม่พิมพ์ไว้ที่ในโฟลเดอร์ goldens ซึ่งอย่างตัวที่ยกตัวอย่างไปก็จะมี success state กับ error state ก็จะได้ 2 รูปตามนี้ ซึ่งรูปที่จะแคปเจอร์ไว้ก็จะเป็นรูปแบบนี้ครับ ด้านซ้ายจะเป็น success state ด้านขวาจะเป็น error state พอเรารันคำสั่ง flutter test สิ่งที่ได้ก็คือถ้า success ก็จะขึ้นเขียว ๆ แบบนี้ เหมือน unit test เลย

แต่ถ้าเผอิญว่าวันใดวันหนึ่งมี developer คนใหม่มาแก้ไขหน้านั้นแล้วทำให้หน้านั้นผิดเพี้ยนไป ทำให้ปุ่มสองปุ่มนี้ห่างกัน เมื่อเรากดรันเทสขึ้นมาสิ่งที่เกิดขึ้นก็คือตรง scenario screen test success จะเกิดการ failed เกิดขึ้น แล้วก็จะบอกว่า pixel ที่ failed มันมี detect ได้ต่างจากของเดิมเท่าไหร่ อย่างตรงนี้จะบอกว่ามี different ต่างกัน 4.28% ประมาณนี้ แล้วก็จะมีไฟล์รูปภาพโผล่ขึ้นมาบอกว่ามี failure เกิดขึ้นนะ ให้มาลองเช็กดูที่โฟล์เดอร์ตรงนี้ เป็นต้น

แล้วพอเอารูปภาพเปิดขึ้นมาดูก็จะแยกออกเป็น 4 รูปภาพตามนี้ครับ ก็จะมี Isolated diff, Masked diff แล้วก็ Master image แล้วก็ Test image โดย Isolated ก็จะบอกว่ามีส่วนตรงไหนที่มันผิดเพี้ยนไปโดยซ่อน component ที่ไม่เกี่ยวข้องออก อย่างตรงนี้ก็จะบอกว่ามีปุ่ม + กับปุ่ม — ที่มี position ผิดเพี้ยนไปนะ โปรดมาเช็กตรงนี้ดู

ส่วน Masked diff ก็คือเอาทุก UI ทุก component ขึ้นมา render หมดเลย แต่ก็จะวงว่าส่วนไหนที่มันผิดเพี้ยนไป ส่วน Master image ก็คือรูปภาพที่เราทำเป็นแม่พิมพ์เอาไว้ ก็จะหน้าตาแบบนี้ ส่วน Test image ก็คือหน้าตาปัจจุบันที่มันเปลี่ยนแปลงไป แล้วก็มาเช็กที่ failure ตรงนี้ได้ว่า UI ที่มันผิดเพี้ยนมันเกิดขึ้นจากตรงไหนแล้วเราจะแก้ไขยังไง หลัก ๆ ในการเทสของ Widget Tests ก็จะมีประมาณนี้ครับ

สุดท้ายหลังจากฟังเรื่องราวของการทำ Flutter Testing มาทั้งหมด พี่ดิวอยากฝากบอกทุกคนว่า การทำ Flutter Testing นั้น จะทำให้ Software ของเรามีความน่าเชื่อถือมากยิ่งขึ้น แล้วก็ในอนาคตการ maintenance เราสามารถกลับมาดูได้ว่าฟังก์ชันที่เราเขียนอยู่ยังถูกต้องหรือไม่ แล้วก็อาจจะกลับมาดูได้ด้วยว่า สมมติเราไปแก้ไขที่ฟังก์ชันนั้นหรือแก้ไข UI ตรงนี้ เราไปทำงานกระทบส่วนไหนหรือเปล่า ทำให้ business มันหายไปหรือเปล่า ซึ่งการทำ testing สามารถมาช่วยในส่วนนี้ได้ครับ

และนี่คือทั้งหมดของ Flutter Testing จากพี่ดิว-ณัฐฤทธิ์ Software Developer ที่มาเล่าแบบละเอียดมาก ๆ บอกหมดไม่มีกั๊กเพื่อทุกคน และ Muze เชื่อว่าทุกคนที่อ่านจนจบ ต้องได้ความรู้กลับไปแบบแน่น ๆ แน่นอน

แล้วพบกันใหม่ในบทความหน้า ใครชอบอย่าลืมกดไลก์ ส่วนใครมีคำถามอยากถามเพิ่ม หรือสงสัยตรงจุดไหน ก็พิมพ์คอมเมนต์เข้ามาพูดคุย กับพวกเรา Muze Innovation กันได้เลยครับ :)

--

--