[หนังสือแปล] Refactoring JavaScript: บทที่ 3

Tanakorn Numrubporn
p4ftech
Published in
9 min readJul 23, 2017

หนังสือเล่มได้รับอนุญาติให้แปลได้เป็นที่เรียบร้อยแล้วจาก Evan Burchard หากใครอยากอ่านต้นฉบับสามารถไปอุดหนุนผู้เขียนได้ที่ Amazon

Testing

เรามาเริ่มคุยกันหน่อยมั๊ยว่า ตกลง Testing มันผิดตรงไหน?

“เขียน Test มันใช้เวลา แค่งานที่ทำอยู่ก็จะทำไม่ทันอยู่แล้ว”

“มันเพิ่ม Code อีกชุดหนึ่งมาเป็นภาระให้ต้อง Maintain”

“แล้วจะมี QA ไปทำไม (ฟะ)”

“ไม่เห็นว่ามันจะตรวจจับ Error ได้เลย”

“ไม่เห็นว่ามันจะตรวจจับ Error ตัวหลักได้เลย”

“แค่แก้ Code นิดหน่อย ไม่เห็นต้องเขียน Test เลย”

“ก็ Code มันเวิร์คอยู่แล้วนี่นา”

“เจ้านาย กับลูกค้าจ่ายค่าแรงให้ผมมาสร้างฟีเจอร์ ไม่ใช่มาเขียน Test”

“ในทีมของผม ไม่มีใครสนใจเรื่องนี้เลย แถมพอเขียน Code จน Test Suite พัง ก็ไม่แก้ ผมเลยต้องทำตัวเหมือน นายตำรวจ เจมส์ กอร์ดอน ในซีรีย์ Gotham ที่บ้าเรื่อง Test อยู่คนเดียว”

About That Last Quote…

จากรายการข้างต้นทั้งหมด ข้อสุดท้ายดูจะเป็นเรื่องที่แก้ยากกว่าข้ออื่น เพราะข้อนี้ปัญหาไม่ได้อยู่ที่ Test แต่เกิดจากทีมขาดประสบการณ์ในเรื่องนี้ อีกทั้งยังไม่มีภาวะผู้นำอีกด้วย การเปลี่ยนให้ทีมเกิดความเข้าใจ จนพัฒนาเป็นทักษะในเรื่อง Test นั้น ต้องค่อยๆ เป็น ค่อยๆ ไป (และต้องใจเย็น โดยต้องรอให้เกิดการเปลี่ยนแปลงทีละคน) หรือบางองค์กรอาจจะเลือกใช้วิธีสั่งการมาจากเบื้องบน (ซึ่งสัมฤทธิ์ผลได้ยากมาก หากทีมขาดภาวะผู้นำ)

แต่หากคุณโชคร้าย ต้องทำงานอยู่ในทีมที่เต็มไปด้วย Coder ประเภท “คาวบอย” ที่เอาแต่ตั้งหน้าตั้งตาเขียน Code อัดเข้าไป โดยไม่สนใจเรื่องคุณภาพ และการ Test เลย ผลลัพธ์ที่มักจะเกิดขึ้นก็คือ ความหงุดหงิดของลูกค้า (และตัว Coder เอง) และระบบที่มีพฤติกรรมแปรปรวน รวมทั้งเวลาในการพัฒนาที่ประเมินได้ยาก

“ไล่ออกยกทีม” ก็ไม่น่าจะใช้แนวทางแก้ปัญหาที่ดี เพราะแต่ละทีมก็มีข้อจำกัด และจุดแข็งที่แตกต่างกันไป แค่การขาดทักษะเขียน Test ไม่น่าจะเป็นสาเหตุของการไล่ออก ผู้นำที่ต้องการคุณภาพ อาจจะต้องมองหาแนวทางที่เน้นให้ทีมมีเสถียรภาพ, ลดการลาออกของทีม, ระบบผลตอบแทนที่สมเหตุสมผล และโอกาสที่ทีมจะได้ฝึกฝน เรียนรู้

จากรายการข้ออ้างยอดนิยมในการไม่เขียน Test ข้างต้นทั้งหมด ข้อสุดท้ายโดดเด่นที่สุด และไม่สามารถแก้ไขได้ง่ายๆ โดยแค่พูดส่งๆ ไปว่า “เดี๋ยวลองเขียนไปซักพักจนคล่อง จะรู้เองแหละว่าการเขียน Test มันดี และสนุกยังไง” เพราะปัญหามันอาจจะหยั่งรากลึกไปถึงระดับวัฒนธรรมการทำงานที่ไม่ให้ความสำคัญกับการ Test (มองอีกมุมคือ ไม่ให้ความสำคัญกับคุณภาพ) การแก้วัฒนธรรมไม่สามารถทำสำเร็จได้ด้วยคนทำงานเพียงคนเดียวเท่านั้น ดังนั้น หากคุณไม่ได้อยู่ในระดับ Management ทางที่ดีก็คือ ลองมองหาทีมที่มีกรอบความคิดตรงกับคุณ แล้วเปลี่ยนทีมซะ

มองเผินๆ ข้อที่บอกว่า “เจ้านาย กับลูกค้าจ่ายค่าแรงให้ผมมาสร้างฟีเจอร์ ไม่ใช่มาเขียน Test” เหมือนจะเป็นประเด็นด้านวัฒนธรรมการทำงาน แต่หากมองดีๆ ปัญหาข้อนี้ไม่ได้ลงลึกถึงระดับ Code ซึ่งหากว่าคุณเป็นคนที่เขียน Test คล่องอยู่แล้ว แค่เข้าไปลุยทำให้ Test มันเกิดขึ้น ก็แก้ปัญหาได้แล้ว วิธีการก็แค่ใช้ดุลยพินิจของคุณในการสร้าง Quality Code ด้วยตัวเองไปเลย เพราะคงไม่มี Boss หรือลูกค้าสติดีคนไหนจะปฏิเสธกระบวนการในการตรวจสอบซอฟต์แวร์หรอกครับ หากสามารถ Automate ได้ ก็ให้ Automate ระบบ Test เสียแต่เนิ่นๆ แต่สำหรับคนที่ยังขาดทักษะด้านการ Test ก็อาจจะต้องยอมลดมาตรฐานด้านคุณภาพไปพลางๆ ก่อน จนกว่าตัวคุณจะมีประสบการณ์เพียงพอ แล้วหันไปใช้วิธี Test แบบ Manual เอา

ทั้งหมดที่ผมจะพูดก็คือ แนวทางในการจัดการกับแรงต้านภายในทีมในเรื่อง Test ก็คือ ทำให้สมาชิกในทีมคุ้นเคยกับการ Test ไปพร้อมๆ กับการค่อยๆ ยกระดับมาตรฐานคุณภาพซอฟต์แวร์ในเวลาเดียวกัน ส่วนแรงต้านที่มาจากภายนอกนั้น (ข้อตกลงเรื่องคุณภาพ และการไม่มีนโยบายฝึกอบรมทีมงาน) หนังสือเล่มนี้จะเน้นไปที่การจัดการกับแรงต้านภายในเท่านั้น ส่วนแรงต้านภายนอกเป็นเรื่องของทักษะด้านการเมือง และการเลือกโปรเจกท์ไปทำงาน

หากคุณเคยเจอกับข้ออ้างเหล่านี้ ดีใจด้วยครับ คุณมีเพื่อนเยอะเลย เพราะการเขียน Test มันยากจริงๆ นั่นแหละ แต่ที่ว่ายากนั้น ไม่ใช่การ Test ที่ยาก แต่เป็นอคติภายในใจของแต่ละคนมากกว่าที่ทำให้มันยาก Coder คนไหนก็ตามที่ต่อต้านการเขียน Test อาจจะกำลังอยู่ในภาวะ “Cognitive Dissonance” ซึ่งผู้ที่อยู่ในภาวะนี้จะพยายามหาหลักฐานทุกอย่างมาเพื่อสนับสนุนความคิดตัวเอง (ว่า Test เป็นสิ่งไม่มีประโยชน์/ต้นทุนสูง/ไม่เกี่ยวกับเรา) ต่อมาคุณก็จะเริ่มท้อ แล้วทำตัวเหมือนสุนัขจิ้งจอกในนิทานเรื่องอีสปเรื่อง องุ่นเปรี้ยว ที่พอรู้สึกว่าการใช้ Test เพื่อรับรองคุณภาพซอฟต์แวร์เป็นเรื่องยากเกินกว่าที่คิดไว้แต่แรก ก็เลยเหมาเอาว่ามันไม่ใช่เรื่องสำคัญซักเท่าไหร่

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

จุดประสงค์หลักของการเขียน Test ก็เพื่อทำให้คุณเกิด ความเชื่อมั่น ต่อ Code ของตัวเอง ซึ่งเจ้าความเชื่อมั่นตัวนี้ไม่ได้จู่ๆ ก็จะเกิดขึ้นมาเองได้ มันเริ่มมาตั้งแต่วันที่เรายังมอง Code แบบจับผิด ซึ่งเป็นวันที่ Code ยังเต็มไปด้วย Error และปรับเปลี่ยนยาก ความเชื่อมั่นคือเหตุผลที่สำคัญที่สุดที่ทำให้เรา Test อย่างไร (เมื่อเหตุชัด เรื่องวิธีการก็ไม่น่าจะยากแล้ว) เหตุผลที่ทำให้เราต้องลงแรงไปฝึกเขียน Test ก็เพื่อพัฒนาให้ตนเองรู้จักว่า ความเชื่อมั่นคืออะไร และทำให้เราเป็นคนที่ “จับผิดเก่ง” ฟังแล้วอาจจะดู Abstract นิดนึง แต่อย่าเพิ่งกังวลไปครับ เพราะเดี๋ยวเราจะพูดกันถึงเหตุผลในการ Test อย่างจริงจังในเนื้อหาต่อจากนี้ไป

แต่ก่อนอื่น ผมขอเล็กเชอร์คุณอ่านให้เข้าใจถึงคำศัพท์บางตัวซักเล็กน้อย

Coverage (อาจจะเรียกเป็น Code Coverage หรือ Test Coverage): เป็นตัวเลขที่ชี้วัดว่า มี Code กี่บรรทัดที่คุมด้วย Test โดยมักจะมีหน่วยเป็นเปอร์เซ็นต์

High-Level และ Low-Level: Test Code สามารถเขียนได้ทั้งแบบ High-Level และ Low-Level ซึ่ง High-Level แปลว่า “End-to-End Test” ส่วน Low-Level จะหมายถึง “Unit Test” ทั้งสองเป็น Test ที่หนังสือเล่มนี้ให้ความสำคัญอย่างมาก

Complexity: คือตัวเลขที่ใช้ในการประเมิน Path ต่างๆ ภายใน Code ซึ่งใช้ในการชี้วัดความซับซ้อนของ Code อย่างกว้างๆ แต่ไม่ลงลึกมากเท่ากับบรรพบุรุษของมันที่ชื่อว่า Cyclomatic Complexity

Confidence: นี่คือเหตุผลสูงสุดที่ทำให้เราต้อง Test โดยการที่เราทำจนเกิด Full Test Coverage จะช่วยเพิ่มความเชื่อมั่น (Confidence) ให้สูงขึ้น แต่การที่ Coverage จะมีคุณภาพพอให้เราเชื่อได้นั้น Test ควรจะครอบคลุมทั้ง “Nonfunctional Testing” และ “Test-Driven Development”

Exercised: Code บรรทัดไหนก็ตามที่ถูก Exercise แปลว่าถูกรันโดย Test Suite ซึ่งจะถูกนำไปคำนวณเป็น Coverage

Technical Debt: เมื่อสิ่งนี้เกิดขึ้น ย่อมจะกระทบต่อ Confidence ใน Codebase (ที่มี Complexity สูง และไร้ซึ่ง Test Coverage) ส่งผลให้กระบวนการพัฒนาจะช้าลงเรื่อยๆ

Feedback Loop: คือ ระยะเวลาที่เกิดขึ้นตั้งแต่ช่วงเขียน Code ไปจนถึงจังหวะที่ รู้ว่ามันทำงานถูกต้องหรือไม่ ยิ่ง Feedback Loop สั้นเท่าไหร่ ยิ่งดีเท่านั้น เพราะมันจะทำให้คุณรู้ตัวเร็วขึ้นว่า Code ที่คุณเขียนขึ้นมา ทำงานได้ตามที่ตั้งใจไว้หรือไม่

Mocking และ Stubbing: ทั้งสองต่างช่วยให้ Test หลบการ Exercise Code ที่ไม่ต้องการ โดยนำเอา Function ปลอมมาใช้แทน Mocking จะต่างจาก Stubbing ตรงที่ Mocking จะเป็นตัวสร้าง Assertion (ใช้สำหรับแสดงผลว่า pass หรือ fail) ซึ่ง Stubbing ทำไม่ได้

พันหมื่นเหตุผลที่เราต้อง Test

1. คุณก็ทำมันอยู่แล้ว (โดยไม่รู้ตัว)!

จริงๆ ผมอาจจะเดาไปเองก็ได้ แต่ถ้าหากว่าคุณเป็นคนหนึ่งที่รัน Code ใน Console หรือบน Browser เพื่อตรวจสอบพฤติกรรมของ Code ก็แปลว่าคุณได้ทำ Test ไปแล้ว ถึงแม้จะเป็น Test ที่ดูจะรันได้ช้า และมีโอกาสเกิด Error มากไปซักหน่อยก็เหอะ ให้ไปดูตัวอย่างการ Test แบบนี้ในหัวข้อ “Manual Testing”

2. เราไม่มีทาง Refactoring ได้หากไม่มี Test

อย่างที่เราได้พูดไปแล้วในบทที่ 1 ว่า เราไม่สามารถ Refactoring ได้หากไม่มีการรับประกันพฤติกรรมของ Code ซึ่งตัวรับประกันที่ว่าก็คือ Test นั่นเอง อย่าลืมนะครับ เป้าหมายของการ Refactor ก็คือการปรับปรุงคุณภาพของ Code

3. มันทำให้ทีมทำงานร่วมกันง่ายขึ้น

หากเพื่อนร่วมงานของคุณเขียน Code พังๆ ขึ้นมาชิ้นหนึ่ง Test Suite ก็ควรจะบอกให้คุณรู้เสียแต่เนิ่นๆ

4. Test คือเครื่องมือระดับเทพในการ Demo การทำงานของ Code

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

5. Test ทำได้มากกว่าการตรวจสอบพฤติกรรมของ Code

Library แต่ละตัวต่างก็มีนโยบายในการอัพเดตเวอร์ชั่นที่แตกต่างกัน คุณอาจจะเผลออัพเกรด Library อย่างไม่ตั้งใจจนทำให้เกิด Version Conflict และหาก Code ของคุณไม่มี Test คอยคุม ก็เตรียมตัว เตรียมใจรับชะตากรรมที่เกิดขึ้นได้เลย และหากเมื่อใดที่คุณนำ Library ใหม่ๆ เข้ามาใช้งาน ใจลึกๆ ของคุณก็ย่อมจะต้องการให้ Library ดังกล่าวมี Test ที่เขียนขึ้นมาเพื่อรับประกันการทำงานของมันอย่างแน่นอน? แล้วหากจู่ๆ คุณมีความจำเป็นต้องแก้ Code ของ Library ล่ะ? คุณจะยังวางใจใน Test ของ Library ดังกล่าวได้อยู่มั๊ย?

6. Test คือชิ้นส่วนที่มีความสำคัญต่อการอัพเกรดระบบครั้งใหญ่

ในเวลาที่คุณต้องการใช้ Framework เวอร์ชั่นใหม่ล่าสุด คุณจะอัพเกรดโดยไม่เปลืองเวลาไล่ Code ได้ยังไง? หากคุณเลือกใช้วิธีแบบ Manual หรือการอัพเกรดด้วยมือเปล่า งานที่คุณต้องเจอก็คือ การตรวจ Code แทบทุกบรรทัด เพื่อดูว่า พฤติกรรมของระบบยังเหมือนเดิมหรือไม่หลังจากอัพเกรดไปแล้ว ซึ่งใครเคยผ่านกระบวนการนี้มา ย่อมรู้ซึ้งดีว่า มันเจ็บปวด และกินเวลานานขนาดไหน แต่หากคุณมี Test Suite คอยคุ้มครอง Code อยู่ งานดังกล่าวจะง่าย และเร็วขึ้นอีกมาก

7. คุณจะเจอ Bug ได้เร็ว

ยิ่งเจอ Bug ไวเท่าไหร่ ยิ่งแก้ง่ายเท่านั้น และยิ่งหากเจอ Bug ก่อนลงมือเขียน Code ด้วยแล้ว ยิ่งสุดยอด หากเรา (Coder) ปล่อยปละละเลยจน Bug ไปโผล่ที่หน้า QA หรือ PO นั่นหมายความว่า จะต้องมีคนเสียเวลาไปแก้ไข แล้วยิ่งหากมันไปโผล่ตรงหน้าลูกค้าด้วยแล้ว นั่นแปลว่า เราอาจจะต้องเริ่มวงจรการผลิตรอบใหม่ทั้งรอบ เสียทั้งชื่อเสียง และความเชื่อมั่นของลูกค้า

8. Test ช่วยให้กระบวนการพัฒนาซอฟต์แวร์มีความละมุนมากขึ้น

กระบวนการ Testing, Refactoring, ปรับปรุงคุณภาพ และจบด้วยการลดการใส่ Technical Debt เข้าไปในระบบให้น้อยที่สุด ถือเป็นเสมือนชุดเกราะที่จะช่วยปกป้องคุณในยามที่จำเป็นต้องเร่งความเร็ว เพื่อจะได้ไม่ต้องมีข้ออ้างว่า “ก็งานมันเร่งนี่นา เลยมีพลาดกันบ้าง” เพราะข้ออ้างแบบนี้ สุดท้ายมันจะไปส่งผลให้คุณต้องใช้เวลากับ Code นานขึ้น, Release ช้า และมีเวลาไปนั่งชิลล์จิบกาแฟน้อยลง

9. Feedback Loop จะสั้นลง

หากคุณพัฒนาระบบโดยปราศจาก Test นั่นเท่ากับว่า คุณกำลังเพิ่ม Feedback Loop ให้ยาวขึ้น ดังนั้น การเขียน Test คือทางออกที่ดีที่สุด ซึ่งหากทำดีๆ อาจจะทำให้คุณรู้ได้ภายในไม่กี่วินาทีว่า Code ของคุณทำงานได้ตามที่ตั้งใจหรือไม่ แต่หากคุณไม่มี Test สิ่งที่คุณทำได้ระหว่างการเขียน Code ก็คงได้แต่สวดอ้อนวอนภาวนาว่ามันจะทำงานได้โดยไม่ผิดพลาด และใช้วิธีตรวจสอบแบบ Manual เอา คำถามคือ หากคุณต้องใช้เวลา 30 นาทีในการตรวจสอบ Code (และจะใช้เวลานานขึ้นอีกหาก Complexity ของระบบเพิ่มสูงขึ้น) คุณจะตรวจสอบบ่อยแค่ไหน? ผมเชื่อว่าไม่บ่อยเท่าไหร่ แต่ยิ่งคุณสามารถบีบ Feedback Loop ลงให้เหลือน้อยได้เท่าไหร่ คุณก็มีโอกาสตรวจสอบบ่อยขึ้นเท่านั้น ส่งผลให้เกิดความเชื่อมั่นโดยอัตโนมัติ

พันหมื่นวิธี Test

ในส่วนนี้ เราจะมาดูถึงวิธีในการ Test แบบต่างๆ ซึ่งทุกๆ แบบต่างก็มี 3 ขั้นตอน เหมือนๆ กัน ดังนี้

  • Setup
  • Assertion
  • Teardown

คำศัพท์ต่างๆ ในโลกของการ Test อาจจะเปลี่ยนไปตามแต่ละ องค์กร, อุตสาหกรรม, ภาษา, Framework และตามช่วงเวลาต่างๆ ในประวัติศาสตร์ ส่วนคำทั้งสามจากรายการข้างต้นทำหน้าที่เป็นการแบ่งหมวดที่เกี่ยวข้องกับการ Refactoring แบบกว้างๆ โดยเราจะขอจำกัดไว้เพียง 3 หมวดไปก่อน ไม่อย่างนั้น รายการคำศัพท์จะยาวมากเกินไป ยกตัวอย่างแค่เรื่อง Manual Test ก็มีมากมายแล้ว ยังไม่นับ “End-to-End test” ที่อาจจะมีบางคนบอกว่าเป็น Integration Test, System Test, Functional Test หรือคำอื่นๆ ขึ้นอยู่กับบริบทในตอนนั้นๆ

เนื้อหาภายในหนังสือส่วนใหญ่จะสนใจ Test ในส่วนที่ช่วยงานของ Refactoring หรือ Test ที่ช่วยป้องกัน และช่วยให้เราสามารถปรับปรุงคุณภาพซอฟต์แวร์ได้ดีขึ้น ไม่ใช่แค่เอา Test มายัดใส่ในระบบให้เสร็จๆ ไป

เวลาเราบอกว่า Codebase ไหนก็ตามมี Full Coverage แปลว่า ทุกๆ Path ภายใน Code ถูก Exercise ไปแล้ว โดยไม่สนใจว่าจะเป็น Unit Test หรือ End-to-End Test แต่หากถูก Exercise ด้วย Test ทั้งสองแบบจะเป็นการดีที่สุด (เราคงไม่เลยเถิดไปถึงระดับต้อง Coverage ใน Code ทุกบรรทัด) ยิ่ง Coverage น้อยเท่าไหร่ Code ก็ยิ่งแย่เท่านั้น แต่ในทางปฏิบัตินั้น การทำ 100% Coverage เป็นเรื่องที่ทำได้ยากมากๆ ยิ่งในโลกของ JavaScript ยิ่งยากขึ้นไปอีก เพราะเป็นธรรมดาที่คุณจะต้องพึ่งพา Library จากบุคคลภายนอกบ้าง เมื่อคุณบรรลุถึงตัวเลข Coverage ที่เหมาะสมแล้ว การพยายามไต่ระดับ Coverage ให้สูงขึ้น จะส่งผลเสียมากกว่าผลดี

การสร้างความเชื่อมั่น ไม่ได้เกิดจาก Testing เพียงอย่างเดียวเท่านั้น ทักษะของผู้เขียน หรือ Domain Expert ที่อยู่เบื้องหลัง Code ก็เป็นสิ่งที่ช่วยเพิ่มความเชื่อมั่นได้เช่นกัน ส่วน Test นั้นมีความพิเศษตรงที่ เป็นเครื่องมือที่ช่วยทำให้ความเชื่อมั่นถูกส่งเข้าไปในเนื้อ Code ตรงๆ ซึ่งสุดท้ายแล้ว ความเชื่อมั่นจาก Code จะกระจายไปสู่ทีมอื่นๆ อีกต่อหนึ่ง Test โดยตัวมันเองนั้นไม่ใช่เป้าหมาย แต่ความเชื่อมั่นต่างหากที่เป็นเป้าหมาย

Manual Testing

โดยสัญชาตญาณแล้ว ทุกๆ คนต่างก็ต้องการ Test ระบบของตัวเองด้วยกันทั้งนั้น หากคุณกำลังพัฒนา Web App สิ่งที่คุณต้องทำแน่ๆ ก็คือโหลดหน้า App ขึ้นมา แล้วคลิกทดสอบ หรือไม่คุณก็อาจจะใช้วิธีเรียก console.log() เพื่อพิมพ์ค่าของ variable ออกมาเพื่อตรวจดูว่ามันถูกต้องหรือไม่ วิธีการ Test แบบนี้มีหลายชื่อเรียก ไม่ว่าจะเป็น “Monkey Testing”, “Manual Testing”, “ตรวจสอบเพื่อความแน่ใจ” หรือ “การทำ QA” ซึ่งเป็นแนวทางการ Test ที่ใช้สำหรับสำรวจระบบ และ Debugging

หากคุณต้องเผชิญกับ Codebase ที่มี Test ต่ำกว่าเกณฑ์ การเข้าไปทดลองระบบด้วยมือ ก็ถือเป็นความคิดที่ดี หรือเราจะทำ Manual Test ในช่วงพัฒนา Feature ใหม่หรือช่วง Debugging ก็ไม่ผิดกติกา เพราะบางครั้งคุณก็จำเป็นต้องเห็นการทำงานของระบบด้วยตาตนเอง การทำงานในขั้นตอนนี้เป็นส่วนหนึ่งในกระบวนการที่เรียกว่า Spiking หรือการวิจัยเทคโนโลยี ซึ่งต้องทำให้เสร็จก่อนจะเข้าวงจรการพัฒนาหลัก แบบ Red->Green->Refactor

Error ที่เกิดขึ้นในตอน Build/Compile ก็สามารถตรวจพบได้ในขั้นตอนนี้เช่นเดียวกัน

Documented Manual Testing

หากเราอยากขยับจาก Manual Test ให้มีความ Automated มากขึ้น ขั้นแรกที่ต้องทำคือ จัดทำแผนการ Test ขึ้นมาเป็นเอกสาร บางคนอาจจะไม่อยากเสียเวลา ขอลุยเขียน Unit Test เลยในทันที แต่ใจเย็นครับ หากยอมสละเวลาซักเล็กน้อยมาเขียนรายละเอียดขั้นตอนในการ Test ออกมาเป็นลายลักษณ์อักษร ก็น่าจะเป็นหนทางที่รวดเร็วที่สุดในการทำให้เกิด Coverage

แม้จะไม่ได้ทำแบบ Automated ก็โอเค ขอแค่รับประกันได้ว่า Code แต่ละ Path ได้รับการ Exercise ทั้งหมด และการระบุขั้นตอนการ Test ออกมาเป็นเอกสาร ก็จะช่วยลด Error ได้มากกว่าการ Test โดยอาศัยความจำเพียงอย่างเดียว เอกสารดังกล่าว ยังสามารถทำหน้าที่เป็น “Checklist” ให้กับทีมพัฒนาได้เข้ามาช่วยกัน Test คนละไม้คนละมือ ถือเป็นการลดภาระของทีม QA ไปในเวลาเดียวกัน หรือหากองค์กรของคุณไม่มี QA วิธีการทำงานแบบนี้ก็ยิ่งเป็นประโยชน์

หากคุณไม่มีความเชื่อมั่นใน Code เวลาจะต้องทำการ Deploy ใหญ่ๆ ซักครั้งนึงก็ใจสั่น กลัวระบบจะมีปัญหา เพราะ Test Suite มันไม่สมบูรณ์ เอกสารตัวนี้จะช่วยคุณได้ เพราะมันจะทำหน้าที่เป็นคู่มือกำกับขั้นตอนการทดสอบที่สามารถนำไปแจกจ่ายให้กับสมาชิกในทีมต่อไปได้

แม้ว่า Code จะมี Coverage ในระดับที่ดีพอก็ตาม แต่ QA หรือ Developer ที่ทำหน้าที่ตรงนี้ ก็ยังอาจจะต้องเลือกที่จะรันระบบด้วยมือเพื่อตรวจสอบว่าทุกอย่างเป็นไปด้วยดี โดยเฉพาะอย่างยิ่งส่วนที่สำคัญต้องไม่พัง (บางคนเรียกวิธีนี้ว่า Smoke Test) หรือบางระบบอาจจะมีส่วนของ Code ที่มีระดับ Complexity สูงจนไม่สามารถทำ Automated Test ได้ แนวทางการทำงานแบบ Documented Manual Test ก็ยิ่งจำเป็นเข้าไปใหญ่

Approval Test

การ Test ด้วยวิธีนี้ เหมาะกับโปรเจกท์บางแบบที่มีความซับซ้อน และ Automated ได้ยาก ยกตัวอย่างเช่น สมมติว่าเว็บไซต์ของคุณมีฟีเจอร์ที่สามารถ Cropping และ Resizing รูป Avatar ได้ การใช้งานจะเริ่มต้นจากการ upload รูปของตนเข้าไปในเว็บ ซึ่งเป็นขั้นตอนที่ไม่น่าจะมีปัญหาอะไรมาก แต่หากคุณต้องการจะทดสอบระบบเพื่อพิสูจน์ว่า Cropping สามารถทำงานได้อย่างถูกต้องหรือไม่ นั่นแหละที่จะเริ่มมีปัญหา คุณอาจจะหาทางออกโดยการ Hardcode รูป input และ output สำหรับการทดสอบ ฟังดูก็รู้ว่ามันเป็น Test ที่เปราะบางอย่างมาก เพราะเมื่อไหร่ที่ Upload รูปใหม่ๆ เข้าไป Test ทั้งหมดก็จะพัง ผลที่สุดแล้ว คุณก็คงจะเลิกรัน Test ไปโดยปริยาย

สำหรับ Approval Test เราสามารถ Automate ขั้นตอนการ Setup และการ Teardown ได้ แต่สำหรับขั้น Assertion (หรือ Approval) จำเป็นต้องใช้มนุษย์เข้ามา Test ด้วยตัวเอง Test ชุดนี้จะบันทึก Output ที่ได้รับการ Approve เอาไว้ แล้วเมื่อไหร่ที่มีการรัน Test ครั้งที่สอง หากผลลัพธ์เหมือนเดิม Test ก็จะ Pass ส่วนในการรัน Test ครั้งต่อๆ ไป (ที่มาพร้อมกับ Output ที่แตกต่างไปจาก Output ของ Test ครั้งแรก) จะถูกเติมเข้าไปในคิวของ “Unapproved” Test ที่รอให้มนุษย์เข้ามาตรวจเอาเอง เมื่อ Test แต่ละตัวภายในคิวได้รับการ Approved แล้ว Output ใหม่ๆ จะถูกนำไปแทนที่ หรือนำไปเพิ่มต่อท้าย Output ตัวเก่าก่อนหน้า หากทุกๆ Output ได้รับการ Approved แล้ว ก็จะพออนุมานได้ว่า Code สามารถทำงานได้อย่างถูกต้อง แต่หากมี Output ที่ไม่ได้รับการ Approved จะถือว่า Code มีสถานะแบบเดียวกับ Test ที่ Fail ที่ต้องได้รับการแก้ไข จากนั้นจึงค่อยใช้ Regression Test เพื่อแก้ Bug ให้หมดไป

กระบวนการนี้จะเหมาะกับ Output ประเภท Image, Video, Audio file หรือ HTML ที่ยากต่อการตรวจสอบผลลัพธ์ด้วยวิธีการเขียน Code แต่หาก Test Output เป็น Text หรือ Page Element เราก็สามารถใช้ End-to-End Test หรือ Unit Test เพื่อทดสอบได้อย่างเต็มที่ โดยอาจจะใช้เครื่องมืออย่าง HTML Parser หรือ Regular Expression Parser เพื่อจัดการกับ Output ประเภท HTML หรือ CSS

หากมองว่า Manual Test คือวิธีการ Test ที่มักจะตัวเลือกแรกๆ ของคนสำหรับ Test ระบบแบบเดี่ยวๆ ดังนั้น Approval Test ก็เป็นวิธีแรกๆ ที่ใครๆ ก็เลือกใช้สำหรับการ Test ระบบแบบกลุ่ม ซึ่งสำหรับทีมที่มีพร้อมทั้ง QA, Product Owner หรือ Designer สามารถให้คนเหล่านี้มาทำ Approval Test ได้ โดยพวกเขาอาจจะ Setup ระบบการ Test ด้วยตนเอง หรือร้องขอให้ Developer ทำการ Demo ให้ดู ขึ้นอยู่กับว่า ทักษะด้าน Technical ของแต่ละคนอยู่ที่ระดับไหน

จุดอ่อนของ Approval Test อยู่ตรงที่ การ Setup ทำได้ยาก และผู้ทดสอบยังต้องแบกภาระในการจำด้วยว่า Output ตัวไหนที่ถูก Approval หรือ Rejected ไปแล้วบ้าง ยิ่งหากมีการเปลี่ยนทีมรับผิดชอบระบบ การส่งต่อความรู้ ทั้งวิธีการ Setup และคิวของ Approval ยิ่งเป็นเรื่องที่ต้องได้รับการดูแลเป็นอย่างดี

บางคนอาจจะพยายามนำขั้นตอนการ Approve ทั้งหมดมาแปลงเป็น Framework ซึ่งนอกจากจะไม่ได้ทำให้กระบวนการมันง่ายขึ้นแล้ว ตัว Tester ยังต้องเรียนรู้วิธีการใช้งานมันด้วย หากจะให้ผมแนะนำ ผมขอแนะนำว่า เอารายการ Approve ทั้งมาสร้างเป็นรายการของ URL ตรงๆ แล้วส่งต่อให้ Tester ไปตรวจด้วยตัวเองจะดีกว่า

มองในมุม Developer แล้ว การ Setup คิวสำหรับ Approve Output เป็นเรื่องยุ่งยาก และน่ารำคาญพอสมควรเลยทีเดียว

Approval Test เป็นกระบวนการที่ได้รับความนิยมอย่างมากสำหรับทีมที่ยังไม่มีแนวทางการ Test ที่ชัดเจน จึงมีบางทีมได้นำ “Approval Test Framework” มาใช้ในการพัฒนา ซึ่งคำถามดังต่อไปนี้ เป็นที่มาและเหตุผลที่ว่า ทำไมทีมดังกล่าวจึงต้องใช้ Framework ประเภทนี้

  • ในเมื่อกระบวนการ Development และ Approval เป็นสองกระบวนการที่แยกจากกัน คำถามคือ เราจะเก็บรายการ Approved/Unapproved ไว้ที่ไหน?
  • ใครคือผู้รับผิดชอบในการแปล Requirement มาเป็นรายการ Approval ดังกล่าว?
  • เราควรจะเก็บ Backlog รวมไว้ใน Source Code หรือไม่?
  • หาก Approval Test เกิด Fail ขึ้นมา เราควรจะเอาผลลัพธ์ดังกล่าวรวมเข้าไปในผลการ Test ทั้งหมดหรือไม่?
  • หากรวม เราจะทำอย่างไรไม่ให้มันไปทำให้กระบวนการ Test มันช้าลง?
  • หากไม่รวม เราจะรู้ได้อย่างไร หากมีใครเข้ามา Reject ตัว Spec. ที่ Approval ไปแล้ว?
  • มันมีโอกาสที่ Developer จะเผลอไปมองว่าการที่ Approval Test เกิด Pass ขึ้นมา แปลว่างานของเขาเสร็จแล้วหรือไม่?
  • หากคุณใช้ระบบ Issue/Feature/Bug Tracker อยู่แล้ว คำถามคือ แล้ว Approval Test Framework ที่นำเข้ามา จะทำงานร่วมกับระบบดังกล่าวอย่างไร?

ทุกๆ คนภายในทีมที่นำ Approval Test Framework มากำกับการทำงาน จำเป็นที่จะต้องทำความเข้าใจ และสามารถตอบคำถามข้างต้นให้ได้ทุกข้อ สำหรับทีมที่พัฒนาระบบประเภท Software as a Service ซึ่งเป็นระบบเดียวที่พวกเขาต้องดูแล การตอบคำถามเหล่านี้ไม่น่าจะยากเย็นเท่าไหร่นัก แต่หากเมื่อไหร่ที่ระบบต้องมีทีมทำงานร่วมกันหลายทีม และแต่ละทีมก็รับผิดชอบ product ที่แตกต่างกัน การสร้างข้อตกลงร่วมกันในเรื่องดังกล่าวจะเป็นเรื่องท้าทายมากขึ้น และอาจจะมีบางคนเริ่มรู้สึกไม่แฮปปี้กับเครื่องมือ และกระบวนการใหม่ตัวนี้ซักเท่าไหร่

Acceptance Testing

บางคนได้ให้คำนิยามว่า “Acceptance Testing” คือ Spec. โดยละเอียดของ User Story หากคุณสนใจ Test ประเภทนี้ ผมขอแนะนำให้คุณไปดูเรื่องของ Cucumber.js น่าจะช่วยได้มาก

ฟังดูเผินๆ การ Test แบบนี้ก็น่าสนใจไม่น้อย เพราะมันมีความชัดเจนกว่า Approval Test แต่หากจะนำมาใช้งานกับ Cross-Functional Team หรือกับ ลูกค้าที่มานั่งทำงาน on-site กับเรา งานนี้ดูแล้วไม่ง่ายเลย

หากเป็นกรณีเลวร้ายที่สุด (และมักเป็นกรณีที่เกิดขึ้นบ่อย) Developer จะต้องเป็นฝ่ายเขียน Test อีกหนึ่ง Layer เต็มๆ (แทนที่จะเป็นหน้าที่ของ Product Owner หรือ ลูกค้า) แต่ในขณะเดียวกัน Product Owner ก็ยังต้องการให้กระบวนการทำงานยังยืดหยุ่นเหมือนเดิม

บาง Framework ถูกสร้างขึ้นมาเพื่อเป็น “Acceptance Test Framework” แต่กลับไม่ได้ใช้ Syntax ที่เป็น “ภาษาคน” ทำให้คนที่ไม่ใช่ Developer ไม่สามารถเขียน Spec. ได้ มันอาจจะช่วยให้เรื่องของการทดสอบ Event ประเภท Click หรือ Log-In ทำได้ง่ายขึ้น แต่ข้อแม้คือ ต้องเขียน Test ด้วย Programming Code เท่านั้น ไม่สามารถใช้ภาษามนุษย์ได้

ถึงแม้ Testing API เหล่านี้จะมีประโยชน์สำหรับ End-to-End Test ก็ตาม แต่ผมก็อยากให้คุณผู้อ่านจงระวัง Framework ที่พยายาม Automate กระบวนการ Acceptance ทั้งหมด เพราะ Framework เหล่านี้พยายามจะช่วยแปลง Requirement ไปเป็น Code แล้วให้ Code เหล่านั้นตอบโจทย์ Requirement โดยอัตโนมัติ ซี่งก็อย่างที่รู้ๆ กันดีอยู่แล้วว่าวงการ Software Engineering มันไม่ได้โลกสวยขนาดนั้น เพราะสุดท้าย บางกระบวนการก็ต้องอาศัยการพูดคุยกันของคนในทีมอยู่ดี

End-to-End Test

และแล้วก็มาถึงเนื้อหาที่ผมต้องการเสียที Test ตัวนี้มีจุดประสงค์เพื่อ Automate การ Manual Test ทั้งหมด ซึ่งก็คือการทดสอบโดยใช้งานระบบจริงแบบเดียวกับที่ลูกค้าใช้งาน ยกตัวอย่างการทดสอบในกรณีของ Web App ซึ่งหมายถึงการ Sign-Up, Click ปุ่ม, เปิดหน้า Web Page, Download File ฯลฯ

Test Code ใน End-to-End Test ควรจะนำมารันร่วมกับ Codebase ในส่วนอื่นๆ และควรหลีกเลี่ยงการทำ Mocking/Stubbing ด้วยประการทั้งปวง ยกเว้นในกรณีที่จำเป็นจริงๆ ด้วยเหตุนี้ ทำให้ End-to-End Test กลายเป็นการทดสอบการทำงานร่วมกันของระบบทุกส่วนภายในระบบทั้งหมด

Test แบบนี้จะเป็นการ Simulate ประสบการณ์การใช้งานของ User ซึ่งค่อนข้างรันได้ช้า ดังนั้น เราควรจะแยก Test กลุ่มนี้ออกจาก Test ที่รันได้เร็วอย่าง Unit Test (เนื้อหาอยู่ในส่วนถัดไป) บางครั้งเราก็เรียก Test ทั้งสองแบบนี้ว่าเป็น High-Level Test และ Low-Level Test ดังที่เราได้กล่าวไปในเนื้อหาตอนต้นของบทนี้แล้วว่า “High-Level” หมายถึง การมองมุมกว้าง เน้นให้เห็นการทำงานโดยภาพรวมว่า แต่ละส่วนทำงานร่วมกันอย่างไร ส่วน “Low-Level” หมายถึงการมุ่งลงไปที่รายละเอียด

Unit Test

คือ Test ที่รันได้เร็ว และโฟกัสเฉพาะการทำงานระดับ “Unit” เท่านั้น คำถามคือ แล้ว Unit คืออะไร? สำหรับบางภาษาอาจจะบอกว่า Unit คือ “Class และ Function ต่างๆ ของมัน” ส่วนใน JavaScript อาจจะเป็น File, Module, Class, Function, Object หรือ Package ก็ได้ ขึ้นอยู่กับวิธีในการ Abstraction ว่าเราออกแบบไว้ที่ระดับไหน แต่โดยสรุปแล้ว Unit Test จะโฟกัสไปที่ Input และ Output ที่เกิดขึ้นจากการประมวลผลของแต่ละ Unit

หากสมมติให้โลกของ JavaScript คือโลกที่เรียบง่าย โดยภาษาจะอนุญาติให้เขียนได้แค่ Class เท่านั้น หมายความว่าเราจะต้อง Test Input และ Output ของแต่ละ Class โดย Test ผ่าน Object ของ Class นั้นๆ

“Private” Function

หากจะพูดกันให้ง่ายๆ ไปเลย เราก็สามารถพูดได้ว่า Unit ของ JavaScript (Class, Function, Module) สามารถเป็นได้ทั้งแบบ “Private” และ “Public” method

สำหรับ Module นั้น Private Method คือ Function ที่ไม่ได้ Export ออกมา ส่วน Class/Object นั้น คุณต้องเขียน Code เพิ่มเพื่อระบุให้ Function บางตัวเป็น “Private” ซึ่งเวลาจะทำการ Test อาจจะต้องเพิ่มขั้นตอนการ Setup แบบประหลาดๆ เข้าไปใน Test Code อีกทีหนึ่ง

โดยทั่วไปแล้ว เราควรจะ Test แค่ Public Method ก็พอ ซึ่งจะทำให้ Method ดังกล่าว โฟกัสไปที่เรื่องของการสร้าง Interface ที่ปรากฎตัวสู่โลกภายนอก ส่วน Private Method นั้น ยังไงก็ต้องถูก Exercise โดย Test อยู่ดี เพราะ Public Method ที่ถูก Exercise ย่อมจะเรียกใช้ Private Method ดังกล่าว วิธีการนี้จะช่วยเพิ่มความยืดหยุ่นให้คุณสามารถเปลี่ยนรายละเอียดของ Code ภายใน Private Method ได้โดยไม่จำเป็นต้องกลับไปแก้ Test Code (ดังที่ได้กล่าวไปแล้วในตอนต้นว่า Test ที่พังทั้งๆ ที่ยังไม่มีการเปลี่ยน Interface ถือว่าเป็น Test ที่เปราะบาง)

ทั้งหมดนี้คือแนวทางโดยคร่าวๆ ในการ Test ที่เน้นระดับ Interface ไม่ใช่รายละเอียดของการ Implement ภายใน ซึ่งทั้งหมดนี้ก็เป็นไปเพื่อการเพิ่มความเชื่อมั่นใน Code หากการ Test Public Method ยังไม่สามารถสร้างความเชื่อมั่นให้กับคุณได้ คุณก็ไม่ต้องสนใจคำแนะนำในส่วนนี้ ซึ่งเดี๋ยวเราจะมาดูรายละเอียดในบทหน้ากันว่า หากเราจะ Test รายละเอียดภายใน Private Method มันจะเป็นอย่างไร

Test แบบนี้จะแตกต่างจาก End-to-End Test อย่างสิ้นเชิง เนื่องจากมันเป็น Test ที่สนใจเพียงแค่พฤติกรรมการทำงานของแต่ละ Unit แบบแยกส่วนกัน เป็น Low-Level Test ดังนั้น เมื่อใดที่เราเจอกับจุดที่ต้องสื่อสารกับ Unit ตัวอื่น เราสามารถ Mock หรือ Stub จุดนั้นได้อย่างอิสระ การทำงานแบบนี้จะช่วยให้เราพุ่งสมาธิไปยังรายละเอียดการทำงานของแต่ละ Unit แล้วให้ End-to-End รับหน้าที่ในการ Test แบบบูรณาการไป นอกจากนี้ Unit Test ยังช่วยให้คุณไม่ต้องทำการ Load Framework ขึ้นมาทั้งก้อน ไม่ต้องโหลด Package ที่คุณยังไม่อยากจะทดสอบขึ้นมา แถมยังไม่ต้องไป Call Remote Service ไม่ต้องไปยุ่งกับพวก Filesystem หรือพวก Database อีก เพราะทั้งหมดที่กล่าวมานี้ คุณสามารถจำลองการทำงานของพวกมันภายใน Test Code ได้เลย ซึ่งจะทำให้ Test มีความเร็วสูง แม้ว่าจะมีปริมาณ Test ที่เพิ่มขึ้นแค่ไหนก็ตาม

Nonfunctional Testing

ในบริบทของการ Refactoring นั้น Nonfunctional Test จะไม่ได้เกี่ยวข้องโดยตรงกับคุณภาพของ Code และ Confidence โดยจะประกอบด้วยรายการดังนี้

  • Performance Testing
  • Usability Testing
  • Play Testing
  • Security Testing
  • Accessibility Testing
  • Localization Testing

การ Test ในหมวดนี้จะส่งผลโดยตรงกับคุณภาพด้านอื่นๆ ของระบบ เช่น Game สนุกมั๊ย? คนทั่วไปใช้โปรแกรมของเราได้มั๊ย? แล้วผู้ใช้ระดับผู้เชี่ยวชาญล่ะ? ประสบการณ์ในการใช้ครั้งแรกน่าสนใจหรือไม่? แล้วผู้ใช้ที่มีปัญหาในการมองเห็นล่ะ? ระบบ Security ที่หละหลวมส่งผลต่อการเกิด Data Breach หรือก่อให้เกิดปัญหากับการใช้งานของหุ้นส่วน หรือลูกค้าหรือไม่?

Unit Test และ End-to-End Test คือสิ่งที่ช่วยเพิ่มทั้ง Coverage, Confidence และโอกาสในการ Refactor แต่การ Test ทั้งสองแบบ ต่างก็ไม่ได้เกี่ยวข้องกับคุณภาพในหมวด Nonfunctional โดยตรง

Nonfunctional Testing ถือเป็นหัวใจของโปรเจกท์เลยทีเดียว แต่เนื่องจากหนังสือเล่มนี้โฟกัสเฉพาะคุณภาพในด้าน Technical เท่านั้น ผมจึงจำเป็นต้องข้ามรายละเอียดในส่วนที่เกี่ยวกับการ Test ในหมวดนี้ไป ทำได้แค่เพียง แนะนำให้คุณต้องหาเวลาไปศึกษาเพิ่มเติมเรื่องพวกนี้ในภายหลังต่อไป

นอกจากนี้ ผมยังอยากให้คุณลองพิจารณาที่จะสร้างทีมที่เน้นความหลากหลายทั้งในแง่ความสามารถ, ถิ่นที่อยู่ และประสบการณ์ชีวิต เพราะทีมแบบนี้จะสามารถกำจัดปัญหาโหดๆ ได้อย่างรวดเร็ว และสามารถสื่อสารกับคนนอกทีมด้วยภาษาที่ชัดเจนมาก

สุดท้ายนี้ Nonfunctional Test คืองานที่เมื่อลงมือทำไปแล้ว อาจจะเป็นเหตุที่ก่อให้เกิดงานใหม่ขึ้น (ไม่ว่าจะเป็น Bugs หรือ Features หรือการปรับแต่งระบบ) ซึ่งจะถูกนำไปเพิ่มใน Product Backlog มีผลทำให้เกิดการเปลี่ยนแปลง Code และ Complexity ที่เพิ่มขึ้น เมื่อหลีกเลี่ยง Complexity ไม่ได้ คุณก็จำเป็นต้องรับมือกับมันด้วยความมั่นใจ และเมื่อคุณมีความมั่นใจ การทำงานของคุณก็จะยืดหยุ่นมากขึ้น แก้ Bug ได้เร็วขึ้น และเพิ่มฟีเจอร์ได้แบบใจไม่สั่น

Test รูปแบบอื่นๆ

Test ทั้ง 3 หมวด ที่กำลังจะกล่าวถึงดังต่อไปนี้ อาจจะต้องเข้าไปแตะในระดับ Implementation ภายใน Code ซึ่งจะพูดถึงรายละเอียดในบทต่อไป โดยทั้งสามหมวดนี้อาจจะเป็นได้ทั้ง High-Level และ Low-Level ดังนี้

Feature Test

เป็น Test ที่ใช้สำหรับทดสอบ Feature ใหม่ ซึ่งจะดีที่สุดหากเราใช้วิธี Test First (TDD: Test-Driven Development)

Regression Test

เป็น Test ที่มีเป้าหมายเพื่อ Reproduce Bug อันเป็นผลมาจากการเปลี่ยนวิธี Implement ของ Code ทำให้เราสามารถมองเห็นสาเหตุและแก้ไขได้ง่าย การ Test แบบนี้จะช่วยรับประกันว่า Bug ตัวเดิมจะไม่เกิดซ้ำขึ้นอีกครั้ง

Characterization Test

Test ชนิดนี้ถูกเขียนขึ้นทดสอบ Code ที่ยังไม่มี Test คุม เพื่อเพิ่ม Coverage เริ่มต้นโดยการสำรวจ Code ผ่านการรัน Test แล้วจบด้วยการเขียน Unit Test หรือ End-to-End Test แบบโดยใช้วิธี TDD ซึ่งหากเราดันไปเจอ Code ที่เขียนเสร็จไปแล้ว (แต่ยังไม่มี Test คุม) คุณมีทางเลือกสองทาง คือ ทางเลือกแรก เขียน Characterization Test บน Code ดังกล่าว หรือทางเลือกที่สอง Rollback ตัว Code ทิ้งไปแล้วเขียน Test First ไม่ว่าคุณจะเลือกใช้วิธีใด ต่างก็ช่วยเพิ่ม Coverage ให้มากขึ้นทั้งคู่

เครื่องมือ และกระบวนการ

ถึงตรงนี้ ผมก็ได้แต่หวังว่า คุณเริ่มเข้าใจถึง “เหตุผลเบื้องหลัง” ของการ Test และเริ่มมองเห็นภาพว่า Unit Test และ End-to-End Test จะช่วยเพิ่มความมั่นใจให้กับ Code ของคุณได้อย่างไร

มีอีกประเด็นหนึ่งที่ผมอยากจะเน้นย้ำไว้ตรงนี้ว่า: Test เป็นเรื่องยาก เราทุกคนต่างก็ต้องเขียน Code กันอยู่แล้ว แต่ตอนนี้ดันมีงานเขียน Test Code เพิ่มเข้ามาอีก เป็นใครก็ต้องหงุดหงิดเป็นธรรมดา

ประเด็นที่สำคัญยิ่งกว่าก็คือ คุณภาพเป็นเรื่องยาก โชคยังดีที่หนังสือเล่มนี้ไม่ทิ้งปัญหาใหญ่นี้ไว้ให้คุณแก้เพียงลำพัง เพราะเราจะบอกให้คุณรู้ถึงวิธีในการสร้างคุณภาพขึ้นมาด้วยตัวเอง โดยบอกให้คุณรู้จักทั้งเครื่องมือ และกระบวนการที่จำเป็น รวมทั้งงานวิจัยต่างๆ ที่สะสมมานานนับครึ่งศตวรรษ เพื่อใช้สำหรับเพิ่มคุณภาพซอฟต์แวร์

แต่เครื่องมือ และกระบวนการต่างๆ ที่จะกล่าวถึงดังต่อไปนี้ อาจจะไม่สามารถนำมาใช้งานได้กับทุกโปรเจกท์ หรือทุกทีม ที่ผ่านมาคุณผู้อ่านอาจจะเคยมีโอกาสพบเจอกับโปรแกรมเมอร์ประเภท “หมาป่าเดียวดาย” ที่ไม่ค่อยสนใจในกระบวนการ, Tools หรือบางคนอาจจะไม่ได้ Test เลยด้วยซ้ำ โปรแกรมเมอร์คนนั้นอาจจะเต็มเปี่ยมไปด้วยความคิดสร้างสรรค์ และทักษะอันสมบูรณ์พร้อม จนบางทีอาจจะทำให้คนนอกวงการเผลอคิดไปว่า “หรือนี่ก็คือ วิถีแห่งโปรแกรมเมอร์ขั้นสุดยอด”

สำหรับซอฟต์แวร์ที่มีความซับซ้อน หากทีมที่อุดมไปด้วย Coder ประเภทหมาป่าเดียวดายมาทำหน้าที่ Maintain ระบบ สิ่งที่จะเกิดขึ้นก็คือ Technical Debt จำนวนมหาศาล ที่แย่ไปกว่านั้นก็คือ แต่ละคนต่างก็กุมข้อมูลของระบบคนละส่วน แต่ไม่สามารถนำมาแชร์กับทีมเพื่อให้เกิดความเข้าใจร่วมกันทั้งหมดทุกคนได้

กระบวนการ และเครื่องมือต่างๆ จะสามารถปรับตัวไปตามระดับ Complexity ของทีม และโปรเจ็กท์ แต่เสียใจด้วยครับ อย่าเสียเวลาไปกับการหาเครื่องมือที่แก้ปัญหาได้แบบครอบจักรวาลเลย เพราะมันไม่มี

กระบวนการคุณภาพ

Coding Standards และ Style Guides

Developer Happiness Meeting

Pair Programming

Code Review

Test-Driven Development

เครื่องมือคุณภาพ

Version Control

Test Frameworks

Assertion/Expectation Syntax Libraries

Domain-Specific Libraries

Factories and Fixtures

Mocking/Stubbing Libraries

Build/Task/Package Tools

Loaders and Watchers

Test Run Parallelizers

Continuous Integration (CI) Services

Coverage Reporters

Style Checkers (Linters)

Debuggers/Loggers

Staging/QA Servers

สรุปส่งท้าย

ถึงตรงนี้ คุณผู้อ่านน่าจะมองเห็นภาพโดยรวมแล้วว่า Test คืออะไร มีประโยชน์ยังไง และเป็นมีความสำคัญต่อการ Refactoring ตรงไหน ในบทต่อไป เราจะมาเริ่มลงมือเขียน Test ภาคปฏิบัติกันดีกว่า จบภาคทฤษฏีเสียที

กลับไปที่ สารบัญ

--

--