มาทำ Unittest ด้วยโจทย์ Fizzbuzz กันเถอะ
วันนี้ผมจะชวนทุกท่านมาลองทำ unittest กัน ผมเชื่อว่า มี Developer หลายคนชอบทำ unittest และยังมีอีกหลายคนที่ไม่ชอบทำเช่นกัน แล้ว unittest มันคืออะไรกันนะ ?
unittest คืออะไร
unittest คือ การเขียนโค้ด (code) เพื่อทดสอบฟังก์ชันโดยมักจะเขียนโค้ดเพื่อทดสอบเป็นระดับฟังก์ชัน เช่น จากฟังก์ชันการคำนวณเลขจำนวนเต็มบวก ต้องการหากรณีที่สามารถเกิดขึ้นได้ จำนวนกรณีที่ต้องการหาขึ้นอยู่กับว่าอยากจะให้การทดสอบครอบคลุมมากขนาดไหน ซึ่งผมขอยกตัวอย่างมา 3 กรณี ดังนี้
1. ทดสอบใส่ตัวเลขสองตัว คือ 5 กับ 8 บวกกัน ผลลัพธ์จะได้คือ 13
2. ทดสอบใส่ตัวเลขติดลบ คือ -1 และ -3 บวกกัน ผลลัพธ์จะได้คือ ไม่สามารถทำการบวกได้ 3
3. ทดสอบใส่ตัวอักขระ A และ B บวกกัน ผลลัพธ์จะได้คือ ไม่สามารถทำการบวกได้
จากตัวอย่างข้างต้น จะเห็นได้ว่า การทดสอบจะประกอบด้วยกัน 3 ส่วน ได้แก่
1. Input ของฟังก์ชั่นคืออะไร
2. ทำอะไร
3. Output ของฟังก์ชั่นคืออะไร
ซึ่งผมมักจะเขียนในรูปแบบดังกล่าว แต่จริง ๆ แล้ว ขึ้นอยู่กับการตัดสินใจวางแบบฟอร์มของแต่ละคนว่าจะเขียนในรูปแบบใด สามารถใช้ชื่อตามฟังก์ชันได้เหมือนกัน ไม่ได้ถูกผิด ขึ้นอยู่กับการใช้งานจริงครับ
สำหรับวันนี้ ผมขอเลือกโจทย์ Fizzbuzz มาลองทำกันนะครับ บางคนอาจจะคุ้นเคยกับ Fizzbuzz แล้ว หรือหากไม่คุ้นก็ไม่เป็นไรครับ มาทำความรู้จักกับ Fizzbuzz กัน
โจทย์
Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.
จากโจทย์ เรามาวางโครงกันก่อนนะครับ
# import lib มาก่อนนะครับ
import unittest# สร้างclass ขึ้นมาจะชื่ออะไรก็ได้ครับ
class FizzBuzz(unittest.TestCase):
if __name__ == '__main__': unittest.main()
วิธีการเขียนมักจะเป็นลูปแบบนี้ คือ Write Testcase -> Write Code -> Refector ซึ่งเป็นแนวทางที่ดี แต่เวลาปฏิบัติจริงนั้น มักจะตรงข้ามกัน เนื่องด้วยความไม่ชิน หรือ ไม่ถนัด ซึ่งต้องการการฝึกฝนและเรียนรู้ด้วยครับ โดยวิธีที่ผมทำในงานจริงนั้นก็คือ Write Code -> Refector ->Write Testcase -> Refector ครับ
แต่ในที่นี้ผมขออนุญาตจะใช้แบบนี้แทนคือ Write Testcase -> Write Code -> Refector โดยโจทย์นี้มันจะมีหลาย testcase ให้คิดครับ ผมจะเริ่มจากเคสง่าย ๆ เลย เช่น ใส่เลข 3 ลงไปผลลัพธ์ที่จะออกมาควรเป็น Fizz โดยมีขั้นตอน ดังนี้
- เขียนชื่อ testcase
- เรียกใช้ฟังก์ชัน calculate_fizzbuzz(3) แล้วให้ส่งค่ากลับมาที่ตัวแปร Actual (ความเป็นจริงจากผลลัพธ์)
- expected (ผลลัพธ์ที่คาดหวัง)
- self.assertEqual (actual, expected) ก็จะเป็นการใช้ฟังก์ชันเพื่อเปรียบเทียบ actual == expected หรือไม่ โดยฟังก์ชั่นในการเปรียบเทียบยังมีอีกหลายแบบขึ้นอยู่กับวิธีการเลือกใช้ครับ
import unittest
class FizzBuzz(unittest.TestCase): def test_input_three_should_return_fizz(self): actual = calculate_fizzbuzz(3) expected = 'fizz' self.assertEqual(actual, expected)
def calculate_fizzbuzz(number): pass
if __name__ == '__main__': unittest.main()
หลังจากที่ผมได้ทดลองรัน unittest ด้วยคำสั่ง python <file name> ก็จะได้ผลลัพธ์โดยสังเกตที่ AssertionError: None != ‘fizz’ ซึ่งมันจะผิดอยู่เนื่องด้วยผมไม่ได้เขียนให้ฟังก์ชัน calculate_fizzbuzz นั้นส่งค่าอะไรกลับมา
จาก error ว่า AssertionError: None ≠ 'fizz' ต่อมาก็จะต้องไปทำการแก้ไขโค้ดให้มันทำงานถูกต้อง โดยอาจจะเพิ่ม return ‘fizz' ไปก็ได้เช่นกัน
def calculate_fizzbuzz(number): return ‘fizz’
สำหรับท่านที่สนใจสามารถดูตัวอย่าง testcase ที่ได้ทำไป และ ฟังก์ชัน สำหรับใช้เปรียบเทียบครับ
การทำ unittest มีประโยชน์มากต่อการทำงานร่วมกันเป็นทีม เปรียบเสมือนเราอยู่ในหมู่บ้าน และมีพวก bug หรือ error จะทำลายหมู่บ้าน แต่คนในหมู่บ้านก็จะหาโล่หรือสิ่งของต่างๆมา block ไม่ให้พวก bug เข้ามา หรือ หากเข้ามาได้ มันก็จะทำลายแค่ส่วน ๆ หนึ่ง เพราะเรายังมีโล่ของคนในหมู่บ้านที่คอย block ไม่ให้ bug เข้ามาทำระบบที่เราสร้างขึ้นมาพัง
หวังเพื่อน ๆ คงจะได้แนวทางไปลองทำ unitest กับโปรแกรมหรือระบบอื่นนะครับ หากสนใจอ่านบทความอื่น ๆ ของผมเพิ่มเติม สามารถอ่านได้ที่ บทความ Deploy FastAPI ไปยัง Heroku ด้วย GitHub Actions ครับ ขอบคุณครับ