Unit test ตอนที่ 4 ทฤษฎีกันยาวๆ

ทฤษฎีกันยาวๆ

ผมเขียนเกี่ยวกับ Unit test ไว้ 4 ตอน คุณสามารถกด Link ด้านล่างเพื่ออ่านที่เกี่ยวกับ Unit test ตอนต่างๆได้เลย

ตอนนี้จะพูดถึงเรื่องทฤษฎีเป็นส่วนใหญ่แล้วแล้วก็ตอนนี้คิดมุกไม่ค่อยออกด้วยอาจจะตอนที่น่าเบื่อสุดๆเลย

Update โปรแกรมกันก่อนนะ

เนื่องจากใช้ junit5 กับ java11 ซึ่งเป็นตัวใหม่พอสมควรตัว eclipse มันเลยมี Bug บ้างซึ่งตอนนี้เขาได้ Fix bug แล้วแต่ eclipse ตัวที่โหลดสดๆจากเว็บอาจจะยังไม่ได้ Update เวลา Run test จะเกิด Error ขึ้นมาได้ ในส่วนนี้หากใครลอง Run แล้วไม่ Error ก็ไม่ต้องทำนะครับ ส่วนใคร Run แล้ว Error ประมาณว่า NoClassDefFound Exception ก็ให้ทำการ update ตัว eclipse ตามลิ้งนี้ได้เลย http://download.eclipse.org/eclipse/updates/4.9-P-builds/

Mock(ing jay) Part 2

จากตอนที่แล้วผมได้อธิบายเรื่อง Mock ในส่วนการจำลองการ Return ค่าต่างๆออกมา จำลองการ Throw exception ออกมาได้แล้ว ต่อไปจะเป็นอีกความสามารถหนึ่งของการ Mock ซึ่งมันคือการตรวจสอบพฤติกรรมของ Mock ของเราได้ ซึ่งนี่จะเป็นการแก้ปัญหากับการ Test Method หรือ Function ที่ไม่ Retrun หรืออยากทราบการทำงานบางอย่างภายในว่ามันได้เรียก Method หรือ Function อย่างที่เราต้องการไหม พูดตรงนี้อาจเข้าใจยากไปดู Code น่าจะเข้าใจง่ายกว่า โดยผมจะยกตัวอย่างงานประมาณว่า ในตัวอย่างที่แล้วที่เขียน DebtManagement ที่ใช้หาลูกค้าที่ติดหนี้ คราวนี้เราได้รับงานใหม่มาให้เขียนส่วนแจ้งลูกค้าว่า “เฮ้ยไปจ่ายหนี้ด้วยนะ” โดยจะแจ้งเฉพาะกับลูกค้าที่ยอดหนี้สูงเกินกำหนดอะไรประมาณนี้ โดยส่วนที่เขียนใหม่เนี่ยจะทำเป็น Runable เพื่อแตกให้อีก Thread ทำงานได้ ซึ่งพอทำเป็น Runable มันจะบังคับให้ไม่สามารถ Return ค่าอะไรออกมา โดยตัว DebtTask ผมออกแบบมันได้เป็น Class diagram ประมาณนี้

โดยตัว DebtTask จะประกอบไปด้วยตัว DebtManagement ตัวนี้มีไว้ใช้ในการดึงข้อมูลบริษัทที่ติดหนี้ >= จำนวนที่กำหนด ส่วน AlertChanel มีไว้ทำการแจ้งลูกค้า โอเคเรามาดู Code ที่เขียน Test ตัว DebtTask ก่อนกันเลย

อธิบาย Code กัน

@BeforeEach

อันนี้เป็น Annotation ที่ใช้บอกว่าให้ Run ตัว Method นี้ทุกครั้งก่อนเข้า Test case ซึ่งอันนี้มีไว้ใช้หลายอย่างเช่น ทำการ Set ค่า Object ต่างๆที่มันต้อง Set ซ้ำๆทุก Test case ซึ่งมันเป็น Duplicate code เขาเลยทำตัว @BeforeEach ขึ้นมา โดยที่ผมเขียนคือผมทำการ Mock ตัว DebtManagement กับ AlertChanel ไว้เลยที่เดียวเลย จะได้ไม่ต้องทำการเรียก mock ทุก Test case

verify

เนื่องจากเราไม่สามารถทำตรวจสอบค่าที่ Retrun ได้ เราเลยต้องหาวิธีตรวจสอบใหม่ซึ่งวิธีนั้นก็ไม่ยากครับ เพราะคนที่เขาสร้างตัว mock ขึ้นมาเนี่ยเขาก็เจอปัญหาแบบที่เราเจอเขาเลยใส่ความสามารถให้มันสามารถ track ได้ว่าตัวมันนั้นถูกเรียกด้วยอะไรบ้าง ด้วยค่าอะไร ซึ่งจากการที่ความสามารถนี้ทำให้เราสามารถตรวจสอบ mock ที่เราส่งเข้าไปได้ว่ามันถูกเรียกจริงไหม ถูกเรียกกี่ครั้ง ถูกเรียกโดยส่ง parameter อะไร เมื่อบวกกับเงื่อนไขต่างๆเช่น ตัวนี้จะต้องถูกเรียกแค่ครั้งเดียวในกรณีปกติ หรือ ส่วนนี้จะต้องไม่ถูกเรียกเลยถ้าเกิด Error ระหว่างทาง เท่ากับว่าเราสามารถ Track ความเป็นไปของ method ที่เรา Test ได้เลย

โดยจากตัวอย่าง

verify(debtManagement, times(1)).findCompanyToAlertDebt(DEBT_VALUE);

อันนี้ผมทำการตรวจสอบว่า debtManagement ถูกเรียกใช้งาน method : findCompanyToAlertDebt ด้วยค่า DEBT_VALUE เป็นจำนวน 1 ครั้งรึเปล่า

verify(alertChanel, times(1)).alert(EXPECTED_MESSAGE);

ส่วนอันนี้ทำการตรวจสอบว่า alertChanel เนี่ยถูกเรียกใช้งาน method : alert ด้วยค่า EXPECTED_MESSAGE เป็นจำนวน 1 ครั้งรึเปล่า

ความแตกต่างระหว่าง Test สองอย่าง

  • Test แบบดูผลลัพธ์ที่ Return ออกมาฝรั่งเขามีศัพท์เรียกกันว่า “State test”
  • Test แบบดูว่าแต่ละส่วนที่ทำงานมีการทำงานอย่างที่เราต้องการไหมฝั่งเรียกว่า “Interaction test”

ซึ่งทั้งสองสามารถใช้งานร่วมกันได้ แต่ The best จริงๆเวลาเขียน ควรจะเป็นแบบ State test เพราะ Test แบบ State test ได้นั้นแปลว่า Code เราไม่ขึ้นสนใจกระบวนการการทำงานด้านใน Function หรือ Method นั้นเลย มันจึงเป็น Encapsulation ที่สมบูรณ์แบบ คุณจะแก้ลำดับก่อนหน้าการทำงาน จะเรียกตัวนี้ ตัวนั้นไหมก็ไม่มีผล (จริงๆก็มีผลตอน Arrang อะนะ 555+) เพราะเราสนใจแค่ค่าที่ Return ออกมา แต่ถ้าเป็น Interaction test มันจะเป็นการตรวจสอบการทำงานของ Function หรือ Method นั้นว่าเรียกตัวนั้นตัวนี้จริงไหม ซึ่งมันเป็นการละเมิด Encapsulation เพราะเราไปวุ่นวายกับกระบวนการทำงานต่างๆ แต่ถ้ามันจำเป็นต้องเขียนแบบ Intersection test ก็เขียนเถอะครับ ดูตามความจำเป็นอย่าเอากฏมีบีบบังคับการทำอะไร ตัดสินใจตามความจำเป็นและสถานการณ์ ถ้าไม่มั่นใจเขียน Comment ตัวโตๆหรือเขียนไว้ในเอกสารว่าทำไมถึงทำแบบนี้เพื่อให้คนที่เขาตามมาข้างหลังเข้าใจว่าทำไมทำแบบนี้ (เขาจะได้ไม่ด่าพ่อเราเวลามาทำงานต่อ)

Dependency injection

ถ้าคุณไล่ตามอ่าน Code ที่ผมเขียนคุณจะเริ่มเห็นรูปแบบการเขียน Code ตรง constructor ที่ผมจะส่งตัว Object ที่มันจำเป็นต้องใช้เข้าไปใน Object ตัวที่ทำงาน ตัวอย่างเช่น

DebtTask debtTask = new DebtTask(debtManagement, DEBT_VALUE, alertChanel);

ตัว DebtTask ผมจะส่ง DebtManagement กับ AlertChanel เข้าไป

DebtManagement debtManagement = new DebtManagement(customerDebtDao);

ตัว DebtManagement ผมจะส่งตัว CustomerDebtDao เข้าไป

ไอกระบวนการนี้แหละมันเรียกว่า Dependency Injection

Dependency

หมายถึง Component , Module, Object ที่ตัว Class ของเราจำเป็นต้องใช้ ยกตัวอย่างเช่น DebtManagement นั้นต้องการใช้ CustomerDebtDao ในการหาข้อมูลลูกค้าที่ติดหนี้ ซึ่ง DebtManagement จะทำงานไม่ได้หากไม่มี CustomerDebtDao หรือ DebtTask จะทำงานไม่ได้หากขาดตัว DebtManagement กับ AlertChanel

Injection

คือการฉีดเข้าไปอันนี้หมายถึงแทนที่จะให้ตัว Class ที่ทำงานทำการสร้าง Object ขึ้นมาในตัวเองเช่น สั่ง new ใน constructor จะเปลี่ยนมาเป็น ส่งมันเข้ามาจากข้างนอกเองเลยเหมือนกับฉีดเข้าไป จากตัวอย่างผมฉีดมันเข้าไปตอนสั่ง new DebtManagement(customerDebtDao); โดย injection นั้นทำได้หลายแบบ โดยตัวที่ผมใช้การ inject ผ่าน constructor อีกวิธีนึงคือใช้ setter และอีกวิธีคือการให้เป็น parameter ของ method ไปเลย โดยแต่ละวิธีมีข้อดีข้อเสียต่างกันไปแล้วแต่ความเหมาะสมในแต่ละงาน โดยท่านสามารถอ่านเพิ่มเติมได้จาก https://medium.com/@samueleresca/dependency-injection-overview-31b757a8dd51 ซึ่งเขียนได้ดีมากครับ

ข้อดีของการทำแบบนี้

ข้อดีของการทำแบบนี้มันทำให้โปรแกรมมีความยืดหยุ่นขึ้นมากล่าวคือคุณสามารถ Object ที่มีคุณสมบัติตรงตามที่ Class นั้นต้องการไปทำงานได้ ซึ่งคุณสมบัติตรงในที่นี้หมายถึง method หรือ Field ตามที่ Class นั้นต้องการ ซึ่งมันเปิดกว้างเพราะไม่ได้บอกว่ามันต้องทำงานยังไง เช่น CustomerDebtDao นั้นขอแค่ไปดึงค่าของลูกค้าออกมาได้ ไม่ได้กำหนดว่าให้ไปเอามาจากไหน จะไปเอาจาก Database ไปเอาจากไฟล์ ไปเอาจาก WebService ด้านนอก หรือ สุ่มออกมา อะไรก็เกิดขึ้นได้ซึ่งมันเป็นข้อดีของ Polymorphism ซึ่งเรียนมาอย่างนานแล้วก็สงสัยว่ามันมีประโยชน์ตรงไหนซึ่งก็ได้มาเข้าใจวันที่ได้ทำอะไรที่มันต้องยืดหยุ่นมากๆเนี่ยแหละ ซึ่งด้วยการทำ Dependency Injection เนี่ยแหละก็ช่วยให้เราสามารถทำ Unit test โดยการส่ง Mock เข้าไปแทนตัวจริงยังไงล่ะ พอส่ง mock เข้าไปเราก็สามารถให้ Return อะไรก็ได้ตามใจเราปรารถนา หรือ ตรวจสอบการที่มันถูกเรียกได้ นี่แหละมันจึงมีคนเขียนว่า ถ้าคุณเขียน Code ให้ Unit test ได้ Code ของคุณก็จะหยืดหยุ่น

มีข้อดีก็มีข้อเสีย

ข้อเสียของการทำแบบนี้คือคุณจะต้อง Config เยอะมากตอนเรียกใช้ Class คือ ต้องสร้างตัวโน้นตัวนี้ให้เรียบร้อยก่อนแล้วส่งเข้าไปใน Class นั้นอีกที ซึ่งมันซับซ้อนมากกว่าการเขียนสร้างใน Class แล้วก็เวลาเรียกใช้งานบางทีเราไม่รู้จริงๆว่าตัว Class นั้นตอน Run จริงมันถูก Inject อะไรเข้าไปบ้าง ใช่ส่วนที่เราต้องการจริงไหม

จบตอนนี้แค่นี้

เป็นอันจบอีกตอนซึ่งตอนนี้ผมว่าถ้าอ่านกันมาขนาดนี้ก็น่าจะเข้าใจหลักการแนวคิดแล้วก็เห็น Code เบื้องต้นจนไปเขียนกันเองได้แล้ว เลยไม่แน่ใจว่าจะเขียนตอนต่อที่เอางาน FTP มาสอนต่อด้วยไหม จริงๆคือเริ่มหมดอารมณ์เขียนเกียวกับ Unit test แล้ว เอาเป็นว่าถ้ามีอารมณ์หรือมีไฟจะกลับมาเขียนตอนต่อละกัน และทิ้งท้ายแบบในหนัง Ocean’s Thirteen

เจอกันเมื่อเจอกัน

เพลงประกอบการเขียนบทความนี้

เป็นเรื่องที่โคตรจะแปลกที่เพลงนี้ดังไม่เท่าคุ้กกี้เสี่ยงทาย คืองงจริงๆนะแบบความหมายก็ดีกว่าทำนองก็ดีกว่า แต่ช่างมันเถอะครับ เพลงนี้โดยส่วนตัวชอบแบบที่ไม่รู้เลยว่า BNK48 ร้อง คือดูละครเรื่อง shoot i love you แล้วเพลงประกอบละครมันแบบ เฮ้ย โอเคเลย เลยไปหา MV ดูเลย อ้อ BNK48 ร้อง แต่ MV นี่รอดูฟรังอย่างเดียวเลยนะ 555+