目次
1. Python unittest คืออะไร?
unittest
เป็นเฟรมเวิร์กการทดสอบหน่วยที่รวมอยู่ในไลบรารีมาตรฐานของ Python ซึ่งเป็นเครื่องมือสำคัญในการรับประกันคุณภาพของโค้ด ทำให้ผู้พัฒนาสามารถทดสอบแต่ละส่วนของโค้ดได้อย่างอิสระและช่วยให้ค้นหา bug ได้ตั้งแต่เนิ่นๆ นอกจากนี้ยังช่วยตรวจสอบว่าการเปลี่ยนแปลงโค้ดในระหว่างการพัฒนาอย่างต่อเนื่องไม่ได้ทำลายฟังก์ชันที่มีอยู่ความสำคัญของการทดสอบหน่วย
เมื่อโค้ดซับซ้อนมากขึ้น การตรวจสอบว่าต่างส่วนทำงานร่วมกันอย่างถูกต้องนั้นยากขึ้น การนำการทดสอบหน่วยมาใช้ทำให้ป้องกันบั๊กที่ไม่คาดคิดจากการเปลี่ยนแปลงเล็กน้อยได้ง่ายขึ้นและช่วยรักษาเสถียรภาพของโปรแกรมโดยรวม2. วิธีการใช้ unittest เบื้องต้น
unittest
พื้นฐานคือการสร้างคลาสที่สืบทอดจาก unittest.TestCase
แล้วกำหนดเมธอดทดสอบภายในคลาสนั้น ในเมธอดทดสอบจะใช้เมธอด assertion เช่น assertEqual()
เพื่อเปรียบเทียบผลลัพธ์ที่คาดหวังกับผลลัพธ์จริงตัวอย่างการทดสอบพื้นฐาน
โค้ดต่อไปนี้เป็นตัวอย่างง่าย ๆ ที่ทดสอบฟังก์ชันadd(a, b)
import
# โค้ดที่ต้องทดสอบ
def add(a, b):
return a + b
# คลาสทดสอบ
class TestAddFunction(unittest.TestCase):
def test_add_integers(self):
result = add(2, 3)
self.assertEqual(result, 5)
if __name__ == '__main__':
unittest.main()
ในโค้ดนี้ทำการทดสอบว่า ฟังก์ชัน add()
ทำงานอย่างถูกต้องหรือไม่ เมธอด assertEqual()
ตรวจสอบว่าค่าที่คาดหวังและผลลัพธ์จริงเท่ากัน วิธีนี้ช่วยยืนยันว่าฟังก์ชันทำงานถูกต้องในหลายกรณีการขยายการทดสอบ
สามารถใช้เมธอดทดสอบหลาย ๆ ตัวเพื่อทดสอบการทำงานของฟังก์ชันกับอินพุตที่หลากหลาย เช่น การทดสอบจำนวนทศนิยมหรือการต่อสตริงdef test_add_floats(self):
result = add(2.5, 3.5)
self.assertAlmostEqual(result, 6.0, places=2)
def test_add_strings(self):
result = add("Hello, ", "World!")
self.assertEqual(result, "Hello, World!")
ด้วยวิธีนี้ การทดสอบการทำงานของฟังก์ชันกับประเภทข้อมูลที่ต่างกันก็ช่วยยืนยันว่าฟังก์ชันทำงานถูกต้องในสถานการณ์ต่าง ๆ3. วิธีการใช้ setUp() และ tearDown()
เพื่อให้ดำเนินการบางอย่างโดยอัตโนมัติก่อนและหลังการทดสอบ ให้ใช้เมธอดsetUp()
และ tearDown()
ซึ่งจะทำให้สามารถเตรียมการที่จำเป็นก่อนการรันเทสต์และทำความสะอาดหลังจากเทสต์เสร็จสิ้นได้ตัวอย่าง setUp()
setUp()
เมธอดเป็นเมธอดที่เรียกใช้เสมอก่อนแต่ละเมธอดทดสอบ ทำให้สามารถรวมขั้นตอนการเริ่มต้นที่ใช้ร่วมกันได้def setUp(self):
self.temp_value = 42
ตัวอย่าง tearDown()
tearDown()
เมธอดจะทำงานหลังจากแต่ละเมธอดทดสอบ เพื่อทำการทำความสะอาดหรือปล่อยทรัพยากร เช่น การตัดการเชื่อมต่อฐานข้อมูลหรือการลบไฟล์ชั่วคราวdef tearDown(self):
self.temp_value = None
ด้วยวิธีนี้จะช่วยลดความซ้ำซ้อนของโค้ดทดสอบและรักษาโค้ดให้สะอาดยิ่งขึ้น4. การทดสอบความขึ้นอยู่ด้วยการใช้ Mock
เมื่อโค้ดที่ต้องการทดสอบพึ่งพาแหล่งข้อมูลภายนอก(ฐานข้อมูล、API เป็นต้น)ในกรณีที่พึ่งพานั้นถูกแทนที่ด้วย mock จะช่วยปรับปรุงความเร็วในการรันเทสต์และทำให้เทสต์สามารถคาดการณ์ได้ หากใช้โมดูลunittest.mock
ของ Python ก็สามารถทำได้อย่างง่ายดายตัวอย่างการใช้ Mock
ในโค้ดต่อไปนี้ ฟังก์ชันที่ใช้เวลานานtime_consuming_function()
ถูกแทนที่ด้วย mockfrom unittest.mock import patch
class TestAddFunction(unittest.TestCase):
@patch('my_module.time_consuming_function')
def test_add_with_mock(self, mock_func):
mock_func.return_value = 0
result = add(2, 3)
self.assertEqual(result, 5)
ในตัวอย่างนี้ เราใช้ mock เพื่อทำการทดสอบโดยไม่เรียก time_consuming_function
ซึ่งช่วยลดเวลาในการทดสอบในขณะเดียวกันก็ได้ผลลัพธ์ที่แม่นยำ
5. การจัดการข้อยกเว้นและการอ้างอิงแบบกำหนดเอง
unittest
ในสามารถทดสอบการจัดการข้อยกเว้นได้เช่นกัน ตัวอย่างเช่น เพื่อตรวจสอบว่าข้อยกเว้นเกิดขึ้นอย่างถูกต้องในสถานการณ์เฉพาะ ให้ใช้ assertRaises()
เพื่อทำเช่นนั้นการทดสอบการจัดการข้อยกเว้น
ในตัวอย่างต่อไปนี้ จะตรวจสอบว่ามีการเกิดZeroDivisionError
def test_divide_by_zero(self):
with self.assertRaises(ZeroDivisionError):
divide(1, 0)
โค้ดนี้ทดสอบว่าเมื่อเรียก divide(1, 0)
จะเกิด ZeroDivisionError
การสร้างการอ้างอิงแบบกำหนดเอง
ในกรณีที่การอ้างอิงมาตรฐานไม่สามารถรองรับได้ สามารถสร้างเมธอดการอ้างอิงแบบกำหนดเองของคุณเองได้def assertIsPositive(self, value):
self.assertTrue(value > 0, f'{value} is not positive')
โดยใช้การอ้างอิงแบบกำหนดเอง คุณสามารถรองรับสถานการณ์การทดสอบที่เฉพาะเจาะจงมากขึ้น6. ฟีเจอร์การค้นหาเทสต์ของ unittest
การใช้ฟีเจอร์การค้นหาเทสต์ของunittest
จะทำให้คุณสามารถค้นหาและรันไฟล์เทสต์ทั้งหมดในโปรเจกต์โดยอัตโนมัติ ฟีเจอร์นี้มีประโยชน์อย่างยิ่งโดยเฉพาะในโปรเจกต์ขนาดใหญ่วิธีใช้การค้นหาเทสต์
เพื่อรันการค้นหาเทสต์ ให้ใช้คำสั่งต่อไปนี้python -m unittest discover
ด้วยคำสั่งนี้ จะทำการรันไฟล์ test_*.py
ทั้งหมดในไดเรกทอรีที่ระบุ หากต้องการระบุไฟล์หรือไดเรกทอรี ให้ใช้ตัวเลือกดังต่อไปนี้python -m unittest discover -s tests -p "test_*.py"
ด้วยฟีเจอร์นี้ คุณสามารถประหยัดความยุ่งยากในการระบุไฟล์เทสต์แต่ละไฟล์ได้ และสามารถจัดการเทสต์อย่างมีประสิทธิภาพแม้ในโปรเจกต์ขนาดใหญ่
7. เคล็ดลับการเพิ่มประสิทธิภาพด้วย unittest
หากความเร็วในการรันเทสต์ช้า ประสิทธิภาพการพัฒนาจะลดลง ที่นี่เราจะแนะนำเคล็ดลับบางอย่างเพื่อปรับปรุงประสิทธิภาพของเทสต์โดยใช้unittest
。เพิ่มประสิทธิภาพไฟล์ I/O
เทสต์ที่ต้องอ่านเขียนไฟล์สามารถเร่งความเร็วได้โดยประมวลผลในหน่วยความจำ ใช้StringIO
เพื่อสร้างอ็อบเจ็กต์ที่ทำหน้าที่เหมือนไฟล์ในหน่วยความจำ ทำให้หลีกเลี่ยงการทำ I/O กับดิสก์。from io import StringIO
class TestFileOperations(unittest.TestCase):
def test_write_to_memory(self):
output = StringIO()
output.write('Hello, World!')
self.assertEqual(output.getvalue(), 'Hello, World!')
ด้วยวิธีนี้ แม้เทสต์ที่ต้องเข้าถึงไฟล์ก็สามารถปรับปรุงความเร็วของเทสต์ได้อย่างมาก。ใช้ Mock
เพื่อจำกัดการเข้าถึงทรัพยากรภายนอก เราสามารถใช้ Mock เพื่อเร่งความเร็วของเทสต์ได้ ซึ่งช่วยหลีกเลี่ยงความล่าช้าจากเครือข่ายฐานข้อมูลและลดเวลาการรันเทสต์ ตัวอย่างต่อไปนี้จะแทนการเรียก API ด้วย Mock。from unittest.mock import MagicMock
class TestApiCall(unittest.TestCase):
def test_api_response(self):
mock_api = MagicMock(return_value={'status': 'success'})
response = mock_api()
self.assertEqual(response['status'], 'success')
ด้วยวิธีนี้ การทดสอบฟังก์ชันโดยไม่พึ่งพาทรัพยากรภายนอกจะทำให้สร้างสภาพแวดล้อมการทดสอบที่เร็วขึ้นและเสถียรยิ่งขึ้น。8. สรุปและขั้นตอนต่อไป
ในบทความนี้ เราได้อธิบายอย่างกว้างขวางตั้งแต่พื้นฐานของการทดสอบหน่วยด้วยunittest
ของ Python การใช้การตั้งค่าและการทำความสะอาด การทดสอบความขึ้นอยู่ด้วย mock ไปจนถึงเทคนิคการปรับปรุงประสิทธิภาพของการทดสอบสรุปประเด็นสำคัญ
- วิธีการใช้พื้นฐาน: สืบทอดจาก
unittest.TestCase
และใช้เมธอด assertion เพื่อสร้างการทดสอบ - setUp() / tearDown(): การจัดการกระบวนการทั่วไปก่อนและหลังการทดสอบช่วยเพิ่มการนำโค้ดกลับมาใช้ใหม่และความอ่านง่ายของโค้ด
- การใช้ mock: สามารถทดสอบฟังก์ชันโดยไม่ต้องพึ่งพาแหล่งข้อมูลภายนอก ทำให้ประสิทธิภาพของการทดสอบเพิ่มขึ้นอย่างมาก
- การค้นพบการทดสอบ (Test Discovery): ฟีเจอร์ที่สะดวกช่วยให้การจัดการการทดสอบในโครงการขนาดใหญ่ง่ายขึ้น
- เทคนิคการปรับปรุงประสิทธิภาพ: การประมวลผลในหน่วยความจำและการใช้ mock สามารถลดเวลาการรันของการทดสอบได้
ขั้นตอนต่อไป
เมื่อคุณเชี่ยวชาญพื้นฐานของunittest
แล้ว ลองท้าทายวิธีการทดสอบขั้นสูงต่อไป เช่น การทดสอบแบบพารามิเตอร์ที่สามารถทดสอบข้อมูลหลายชุดพร้อมกัน หรือใช้เครื่องมือ coverage เพื่อตรวจสอบขอบเขตการทดสอบของโค้ด ซึ่งช่วยเสริมกลยุทธ์การทดสอบของโครงการทั้งหมด นอกจากนี้ การมองหาเฟรมเวิร์กการทดสอบอื่น ๆ เช่น pytest
ก็เป็นทางเลือกที่ดีเพื่อขยายตัวเลือกตามการใช้งาน
การทดสอบเป็นส่วนสำคัญของการพัฒนา เพื่อค้นหา bug ตั้งแต่เนิ่นๆ และรักษาคุณภาพของโค้ด ควรนำการทดสอบเข้ามาใช้โดยกระตือรือร้น