มาทำความรู้จัก Unit Test สำหรับ Android Developer กันเถอะ

Android Oct 31, 2017

การที่ Android Developer รู้จัก Unit Test ก็เหมือนโอตะ BNK48 ทุกคนที่ต้องรู้จัก
แคปเฌอ >w< (ส่วนโอชิใครก็อีกเรื่องนึง ;P)

โอตะคงลงแอปนี้กันแล้วสินะ ;p แต่ BNK48 ไม่มีเพลงในฟังใจนี่นาาา ;_; หนีไปกิน KFC ดีกว่า

ทำไมไปเปรียบเทียบอะไรแบบนั้นเล่าาาา!

เพราะการทำ Unit Test เป็นหน้าที่ที่พึงกระทำของ Android Developer หน่ะสิ

เรามาทำความรู้จักประเภทของการ Testing ใน Android กันสักนิดนึง

ในเว็บของ Android Developer ได้อธิบาย Fundamentals of Testing โดยแบ่งประเภทการ Test แบบต่างๆ โดยใช้ Testing Pyramid ดังนี้

credit : https://www.youtube.com/watch?v=pK7W5npkhho
  1. Unit Test : small test : 70%

เป็นการ Test ในระดับที่ย่อยที่สุด ตามปกติเราจะใช้ JUnit เขียนกัน เขียนเสร็จก็สามารถรันได้บน Android Studio ได้เลย ตอนนี้ก็มี Mockito ซึ่งเป็นการ mock object ขึ้นมาตัวนึงเพื่อ Test สามารถแบ่งแยกย่อยได้อีก 2 แบบ คือ

เป็นการ Test สำหรับ 1 file เช่น file ของ service ที่เรียก API แสดงชื่อศิลปิน โดยจะต้องไม่เชื่อมต่อกับ server จริง คือเราต้อง mock server นั่นแหละ ดูว่าถ้า Response Code นี้ ผลควรจะออกเป็นอันนี้ เอ้อออ และสามารถ run with coverage ได้ด้วยนะ แต่ส่วนใหญ่มันไม่ได้ coverage 100% หรอกนะ

ทำการทดสอบการเรียกใช้ Resource ต่างๆ บนเครื่องจริงหรือ Emulator ใช้ JUnit, Espresso หรือ UI Automator ในการทำ และสร้าง APK file แยกออกมาอีกตัว เพื่อกระทำการนี้โดยเฉพาะ ตัวอย่างในคลิปนี้เลย

2. Integration Test : medium test : 20%

ใช้ในการทำ service tests, integration tests, hermetic UI และสามารถนำไปทดสอบบน Firebase Test Lab ได้ด้วยนะ

3. UI Test : large test : 10%

อันนี้เป็นการทดสอบการทำงานของหน้า UI ของ Activity/Fragment ต่างๆ ปกติจะทำแค่อันที่ใหญ่ที่สุดอันเดียว ใช้ AndroidJUnitRunner, JUnit4 Rule และ Espresso ในการทำ

วาดสรุปจาก https://developer.android.com/training/testing/fundamentals.html ; ใครเอารูปนี้ไปใช้ให้เครดิตด้วยเน้อ

ปกติจะเน้นให้ Android Developer ทำ Unit Test เอง ซึ่งกินไป 70% ของการ Test ทั้งหมด ซึ่งถือว่าก็ครอบคลุมไปประมาณนึง ไม่สิ ถือว่าเราได้ทำไปเกินครึ่งที่เราควรจะทำ testing หล่ะ

ดังนั้นในบล็อกนี้จะเน้นถึงการ Testing ในส่วนที่ Android Developer ต้องทำ นั่นคือ Unit Test นั่นเองงงง

Fundamentals of Testing | Android Developers
You can also run instrumented unit tests on a physical device or emulator, which doesn’t involve any mocking or…developer.android.com

หลักการสำคัญในการทำ Testing

คือ Test-Driven Development (TDD) นั่นเอง

ลูกศรพางงมาก จริงๆมันควรวนซ้ายมากกว่านะ; credit : https://www.youtube.com/watch?v=pK7W5npkhho

อธิบายจากภาพกว้างๆก่อนนะ

ถ้ามีการเปลี่ยนแปลงโค้ดจากฝั่ง Developer เช่น เพิ่ม feature ใหม่หรือ fixed bug (วงกลมสีเหลืองใหญ่ๆ) เอา Test Case เดิมไป Test ผลจะ Failed เสมอ (วงกลมสีแดง; แต่ถ้าผลออกมา Passed นี่ประหลาดหล่ะ แสดงว่าต้องมีอะไรผิดแน่นอน) และแก้ไขตัว Test Case (วงกลมสีฟ้า)ให้ผลออกมา Passed คือทำงานได้ถูกต้องตรงตามที่เราเขียน (วงกลมสีเขียว)

Red Green Refactor; credit : https://www.youtube.com/watch?v=pK7W5npkhho

ดังนั้น Unit Test ก็จะเวียนวนอยู่ในวงกลมสีเหลือง Red Green Refactor ตามในคลิป Google I/O 2017 นั่นแล

คุณสมบัติของ Unit Test ที่ดี

credit : https://www.youtube.com/watch?v=pK7W5npkhho
  • Thorough ทดสอบอย่างทั่วถึง
  • Repeatable สามารถทำซํ้าได้
  • Focused มุ่งไปในสิ่งที่เราจะทำ test ว่าควรจะได้ผลอะไร
  • Verifies Behaviour รู้ว่าโค้ดส่วนนี้มีพฤติกรรมอย่างไร
  • Fast สามารถ test ได้อย่างรวดเร็ว
  • Concise กระชับ

หลักการการทำ Unit Test

เนื้อหาส่วนนี้เป็นการทบทวนสำหรับ developer ที่ทำ Unit Test กันอยู่แล้ว หรือมือใหม่ หรือคนที่ผ่านเข้ามาอ่านนะ

การทำ Unit Test นั้นจะมีการ compare ค่าสองฝั่ง คือ expect ค่าที่ควรจะเป็น ที่เราคิดไว้ กับ actual ค่าที่ได้จากการทำ Test ซึ่งถ้าเราเขียน test case แล้ว run ผ่าน เมื่อค่าทั้งสองฝั่งจะต้องมีค่าเท่ากัน

ตัวอย่าง เช่น เราทำฟังก์ชั่นบวกเลขแล้วกัน เอาง่ายๆ

public int plusNumber(int a, int b) {
    return a + b;
}

เราจะทดสอบฟังก์ชั่นนี้ว่าทำงานได้ถูกต้องไหม

@Test
public void testPlusNumberShouldGetThree() {
    Assert.assertEqual(3, plusNumber(1, 2));
}

ซึ่งใน Test Case คือฟังก์ชั่น testPlusNumberShouldGetThree ค่า actual ที่เราควรจะได้ คือ 3 นั่นเอง

ก่อนจะเริ่มทำการ Unit Test นั้น เราต้องรู้จักการทำงานของโค้ดในส่วนนั้นๆว่าทำอะไร inputคืออะไร outputคืออะไร
ใส่ค่าแบบนี้เข้าไปแล้วจะได้ออกมาเป็นอะไรในความเห็นส่วนตัวเรานั้น การให้ QA มาทำ Unit Test จึงอาจจะไม่ค่อยเหมาะนัก เพราะไม่รู้ว่า dev เขียนโค้ดชุดนี้ไว้ทำอะไร ควรจะมีชุดทดสอบ หรือ test case อะไรบ้าง
ดังนั้นการทำ Unit Test ควรเป็นหน้าที่ของ developer เพราะรู้ว่าตัวเองเขียนโค้ดอะไร ถ้าทำงานไม่ถูกต้องจะต้องแก้ไขอย่างไร ทำให้การ develop นั้นสามารถทำได้อย่าง smooth และรวดเร็วมากขึ้น

ในการเขียนไฟล์ Test นั้น จะมี @ ต่างๆ นั่นคือ annotation ซึ่งตามหลักเอาไว้จัดระเบียบโค้ดนั่นแหละ ไว้บอกว่าโค้ดก้อนนี้อยู่ในส่วนไหน ในที่นี้เป็นการเรียก interface ของ JUnit นั่นเอง ที่เราเห็นกันหลักๆก็จะมี

  • @Before ส่วนที่ต้องทำก่อน test เช่น mock-up server
  • @Test ตัว test case ซึ่งมีมากกว่า 1 อันแน่ๆ เช่น ถ้า response เป็น 200 หรือ 404
  • @After ส่วนที่ต้องทำหลัง test เช่น shutdown server ที่เรา mock-up ไป

ลำดับการทำงาน ก็จะเป็น @Before -> @Test -> @After แบบนี้ วาดภาพให้เข้าใจง่ายขึ้น ไม่ใช่ @Before -> @Test 1-> @Test 2 -> … -> @Test n -> @After นะ

เส้นจะไปจากบนลงล่าง ตามเลข 1 2 ถึง n ตามจำนวน test case นะ รูปวาดเองใครเอาไปใช้ให้เครดิตด้วยเน้อ

การ mock server คืออะไร?

เป็นการจำลอง server ขึ้นมาตัวนึง โดยการ mock-up มันขึ้นมา แล้ว test service ที่เราสร้างว่า ถ้าเป็น response code ตัวนี้ ควรออกมาเป็นอะไร

การต่อกับ server จริง ไม่ใช่ Unit Test เน้อ เป็น Integration Test แทนนะ เพราะเป็นการ test ระหว่าง function กัน และการ test ก็ใช้เวลามากขึ้นด้วย ซึ่งขัดกับ concept ของ Unit Test นะ

square/okhttp
Square’s meticulous HTTP client for Java and Kotlin. - square/okhttp
อันนี้จดใส่สมุด ในตอนที่ทำ unit test ครั้งแรก โดยจดจากที่พี่แชมป์สอน :)

ตัวอย่างการทำ Unit Test

1. Basic เป็นการ test method ต่างๆ ตอนต้นเราได้กล่าวถึงฟังก์ชั่นบลกตัวเลข ตัวอย่างในที่นี้ก็ขอแตกต่างหน่อยนะ เป็นฟังก์ชั่นต่อ String เข้าด้วยกันเป็น String ใหม่ตัวนึง ชื่อ StringBuilder.java

เราก็มาสร้าง test file ชื่อไฟล์ StringBuilderTest.java โดยเราต้องคิด test case ไว้ก่อนว่าควรมีอะไรบ้าง ซึ่งเราควร test แล้วได้ผลทั้ง null และไม่ null เนอะ แบบ null ก็ลองใส่ไปหลายๆเคส เช่น null String ทั้งหมด, null String บางส่วน ดังนี้

ตามหลักถ้าไม่ผิดอะไรก็จะ Test Passed นะ

2. Model ที่เป็นแบบ POJO ทั้งหลาย

location ของ test file จะอยู่ที่ <module_name>/src/test/java/<module_name>/src/test/java/

ตัวอย่างเอามาจากโปรเจก Sunshine with Architecture Components ที่เป็นตัวอย่างตอนอธิบายการทำงานของโค้ดใน Repository นั่นแหละ โดยเลือกทำ WeatherDao.java

ก่อนอื่นเราต้องรู้จักตัวข้อมูลว่า เมื่อเรียกข้อมูลจาก API จะได้อะไรกลับมาบ้าง ส่วนใหญ่ออกมาในรูปแบบของ json แบบนี้

ดังนั้นการ Test ในส่วนนี้ จะต้อง mock-up data เป็น json ไฟล์ แบบนี้

ในไฟล์ build.gradle ของ module อาจจะต้องเพิ่มสิ่งนี้ไปก่อน

implementation 'com.squareup.retrofit2:converter-gson:2.0.2'

จากนั้นสร้างไฟล์ Test ขึ้นมา เราลองเขียนลอกจากของเก่ามา ใช้ท่า JUnit ปกติเลย หลังจากสร้างเสร็จขึ้น Failed ก็อย่าเพิ่งตกใจนะ มีผิดสักที่แน่ๆ

Error อันนี้แก้โดยใส่ implementation ‘org.hamcrest:hamcrest-junit:2.0.0.0’ ที่ build.gradle ของ module เสร็จแล้วก็จะไปแดงตรงอื่นแทน

บางท่านคงจะทราบแล้วว่าเราเขียน Test Case ผิดตรงไหน อันนี้คืออันที่ถูกต้อง

เราสร้าง TestHelper.java แยกจาก class ที่ใช้ test เพื่อความไม่งงเนอะ ไม่สิ บางทีต้องใช้บ่อย เขียนรวมก็ไม่สวย เขียนแยก class อื่นก็เอาไปใช้ได้ แต่ถ้าจะเขียนรวมก็ได้แหละ ไม่ต้องใส่ annotation นะ ส่วน path ใส่ให้ถูกนะ เป็นอีกจุดที่มักผิดก่อน test จริง เพราะหาไฟล์ไม่เจอนี่แหละ

3. Service ต่างๆที่เชื่อมต่อ API

เราสมมุติว่าเราจะทดสอบการดึง object ตัวนึง ชื่ออะไรดีนะ ชื่อ Blog แล้วกัน มีสองอันที่ไป get มา คือ บล็อกทั้งหมด และคอมเมนต์ในแต่ละบล็อก

หลังจากนั้นเรามาเริ่มทำการ test กัน โดยมี test case หลักๆ แบบนี้

  • ถ้า response code 200, response body ที่ได้ คือ data ก้อนนึง ซึ่งค่าไม่เท่ากับ null
  • ถ้า response code 404 ซึ่งมันก็คือ 404 Not Found หน่ะเนอะ ไม่สามารถรับ data ก้อนนี้กลับมา, response body ที่ได้ คือ null

ดังนั้นเราเขียน test แบบนี้ โดยเราสร้าง class แยก (อีกหล่ะ) นามว่า RestServiceTestHelper.java ไว้เรียก Retrofit เพื่อทำงานกับ service ซึ่งจะรวบไว้ที่ class เดียวกันก็ได้ อันนี้แล้วแต่ว่าใช้บ่อยไหม


สรุป Android Developer ทำ Unit Test ไปทำไม?

เป็นการตรวจสอบโค้ดที่เราเขียนขึ้นมาว่า ทำงานถูกต้องตามที่เราเขียนหรือไม่ โดยเราเป็นคนเขียนโค้ด รู้ดีที่สุดว่าโค้ดส่วนนี้ทำงานอะไร ต้องการ input อะไร และได้ output อะไรกลับมา และทำให้เขียนโค้ดครอบคลุมทุกกรณี(เท่าที่ทำได้)ที่เกิดขึ้นได้ ซึ่ง Unit Test กินเปอร์เซนต์การทำ Testing ไปถึง 70 แล้ว เรียกได้ว่าครอบคลุมการทำ Test ของ Android Application ในระดับหนึ่งแล้ว

แล้ว Unit Test ต่างจาก Integration Test ยังไงอ่ะ?

เราเข้าใจว่ายังงงๆกันอยู่ สำหรับ Developer ที่ไม่ค่อยได้ทำ Test

Unit Test ทำ test แค่ไฟล์เดียว ถ้ามีการเรียกใช้อันอื่นๆ ก็จะ mock หรือ stub ทิ้งไป เช่นตอน test service ก็ mock-up server ไป

Integration Test คือ test ระหว่างสองไฟล์ขึ้นไป ดูความสัมพันธ์กัน เช่น เรียก service โดยต่อ API จริง

ในประเด็นเหล่านี้เราเคยเขียนบล็อกอธิบายเมื่อปีที่แล้ว

SDLC แบบ v-model นั้นดีอย่างไร และ automated testing คืออะไร
SDLC : Software Development Life cycle คือ ระบบการจัดการในการพัฒนา software มี 3 ส่วน คือ ส่วนวิเคราะห์ (analysis), ส่วนออกแบบ (design) และ ส่วนการนำไปใช้ (implementation)

สุดท้ายนี้ หวังว่าบทความนี้จะเป็นประโยชน์ต่อ Developer ทุกท่าน ที่ตั้งใจมาอ่านก็ดี หรือไม่ตั้งใจมาอ่านก็ตาม ได้รู้จัก Test ประเภทต่างๆใน Android และได้เข้าใจถึงการทำ Unit Test มากขึ้นค่ะ :)

Tags

Minseo Chayabanjonglerd

I am a full-time Android Developer and part-time contributor with developer community and web3 world, who believe people have hard skills and soft skills to up-skill to da moon.