เจาะลึกเฟรมเวิร์กการทดสอบของ Django, เปรียบเทียบและเปรียบต่าง TestCase และ TransactionTestCase เพื่อช่วยให้คุณเขียนการทดสอบที่มีประสิทธิภาพและน่าเชื่อถือยิ่งขึ้น
การทดสอบ Python Django: TestCase vs. TransactionTestCase
การทดสอบเป็นส่วนสำคัญของการพัฒนาซอฟต์แวร์ เพื่อให้มั่นใจว่าแอปพลิเคชันของคุณทำงานได้ตามที่คาดไว้และยังคงแข็งแกร่งเมื่อเวลาผ่านไป Django ซึ่งเป็นเว็บเฟรมเวิร์ก Python ที่ได้รับความนิยม มีเฟรมเวิร์กการทดสอบที่มีประสิทธิภาพเพื่อช่วยคุณเขียนการทดสอบที่มีประสิทธิภาพ โพสต์ในบล็อกนี้จะเจาะลึกคลาสพื้นฐานสองคลาสภายในเฟรมเวิร์กการทดสอบของ Django: TestCase
และ TransactionTestCase
เราจะสำรวจความแตกต่าง กรณีการใช้งาน และให้ตัวอย่างที่เป็นประโยชน์เพื่อช่วยคุณเลือกคลาสที่เหมาะสมกับความต้องการในการทดสอบของคุณ
เหตุใดการทดสอบจึงมีความสำคัญใน Django
ก่อนที่จะเจาะลึกลงไปในรายละเอียดเฉพาะของ TestCase
และ TransactionTestCase
เรามาพูดคุยกันสั้นๆ ว่าเหตุใดการทดสอบจึงมีความสำคัญในการพัฒนา Django:
- รับประกันคุณภาพโค้ด: การทดสอบช่วยให้คุณตรวจจับข้อผิดพลาดได้ตั้งแต่เนิ่นๆ ในกระบวนการพัฒนา ป้องกันไม่ให้ข้อผิดพลาดเหล่านั้นเข้าสู่การใช้งานจริง
- อำนวยความสะดวกในการปรับโครงสร้างใหม่: ด้วยชุดการทดสอบที่ครอบคลุม คุณสามารถปรับโครงสร้างโค้ดของคุณได้อย่างมั่นใจ โดยรู้ว่าการทดสอบจะแจ้งเตือนคุณหากคุณแนะนำการถดถอยใดๆ
- ปรับปรุงการทำงานร่วมกัน: การทดสอบที่เขียนอย่างดีทำหน้าที่เป็นเอกสารสำหรับโค้ดของคุณ ทำให้ง่ายขึ้นสำหรับนักพัฒนาคนอื่นๆ ในการทำความเข้าใจและมีส่วนร่วม
- รองรับการพัฒนาที่ขับเคลื่อนด้วยการทดสอบ (TDD): TDD เป็นแนวทางการพัฒนาที่คุณเขียนการทดสอบก่อนเขียนโค้ดจริง วิธีนี้บังคับให้คุณคิดถึงลักษณะการทำงานที่ต้องการของแอปพลิเคชันของคุณตั้งแต่เริ่มต้น นำไปสู่โค้ดที่สะอาดและบำรุงรักษาได้ง่ายขึ้น
เฟรมเวิร์กการทดสอบของ Django: ภาพรวมโดยย่อ
เฟรมเวิร์กการทดสอบของ Django สร้างขึ้นบนโมดูล unittest
ในตัวของ Python มีคุณสมบัติหลายอย่างที่ทำให้การทดสอบแอปพลิเคชัน Django ง่ายขึ้น รวมถึง:
- การค้นพบการทดสอบ: Django จะค้นหาและรันการทดสอบภายในโปรเจ็กต์ของคุณโดยอัตโนมัติ
- ตัวรันการทดสอบ: Django มีตัวรันการทดสอบที่ดำเนินการทดสอบของคุณและรายงานผลลัพธ์
- วิธีการยืนยัน: Django มีชุดวิธีการยืนยันที่คุณสามารถใช้เพื่อตรวจสอบลักษณะการทำงานที่คาดหวังของโค้ดของคุณ
- ไคลเอนต์: ไคลเอนต์ทดสอบของ Django ช่วยให้คุณจำลองการโต้ตอบของผู้ใช้กับแอปพลิเคชันของคุณ เช่น การส่งแบบฟอร์มหรือการส่งคำขอ API
- TestCase และ TransactionTestCase: นี่คือสองคลาสพื้นฐานสำหรับการเขียนการทดสอบใน Django ซึ่งเราจะสำรวจในรายละเอียด
TestCase: การทดสอบหน่วยที่รวดเร็วและมีประสิทธิภาพ
TestCase
เป็นคลาสหลักสำหรับการเขียนการทดสอบหน่วยใน Django มีสภาพแวดล้อมฐานข้อมูลที่สะอาดสำหรับแต่ละกรณีทดสอบ ทำให้มั่นใจได้ว่าการทดสอบจะแยกจากกันและไม่รบกวนซึ่งกันและกัน
TestCase ทำงานอย่างไร
เมื่อคุณใช้ TestCase
Django จะดำเนินการตามขั้นตอนต่อไปนี้สำหรับแต่ละวิธีการทดสอบ:
- สร้างฐานข้อมูลทดสอบ: Django สร้างฐานข้อมูลทดสอบแยกต่างหากสำหรับแต่ละการรันการทดสอบ
- ล้างฐานข้อมูล: ก่อนแต่ละวิธีการทดสอบ Django จะล้างฐานข้อมูลทดสอบ โดยลบข้อมูลที่มีอยู่ออกทั้งหมด
- รันวิธีการทดสอบ: Django ดำเนินการวิธีการทดสอบที่คุณกำหนดไว้
- ย้อนกลับธุรกรรม: หลังจากแต่ละวิธีการทดสอบ Django จะย้อนกลับธุรกรรม โดยยกเลิกการเปลี่ยนแปลงใดๆ ที่ทำกับฐานข้อมูลระหว่างการทดสอบ
แนวทางนี้ทำให้มั่นใจได้ว่าแต่ละวิธีการทดสอบเริ่มต้นด้วยสถานะที่สะอาด และการเปลี่ยนแปลงใดๆ ที่ทำกับฐานข้อมูลจะถูกยกเลิกโดยอัตโนมัติ ทำให้ TestCase
เหมาะสำหรับการทดสอบหน่วย ซึ่งคุณต้องการทดสอบส่วนประกอบแต่ละส่วนของแอปพลิเคชันของคุณแยกกัน
ตัวอย่าง: การทดสอบโมเดลง่ายๆ
ลองพิจารณาตัวอย่างง่ายๆ ของการทดสอบโมเดล Django โดยใช้ TestCase
:
from django.test import TestCase
from .models import Product
class ProductModelTest(TestCase):
def test_product_creation(self):
product = Product.objects.create(name="Test Product", price=10.00)
self.assertEqual(product.name, "Test Product")
self.assertEqual(product.price, 10.00)
self.assertTrue(isinstance(product, Product))
ในตัวอย่างนี้ เรากำลังทดสอบการสร้างอินสแตนซ์โมเดล Product
วิธีการ test_product_creation
สร้างผลิตภัณฑ์ใหม่ จากนั้นใช้วิธีการยืนยันเพื่อตรวจสอบว่าแอตทริบิวต์ของผลิตภัณฑ์ถูกตั้งค่าอย่างถูกต้อง
ควรใช้ TestCase เมื่อใด
โดยทั่วไปแล้ว TestCase
เป็นตัวเลือกที่ต้องการสำหรับสถานการณ์การทดสอบ Django ส่วนใหญ่ รวดเร็ว มีประสิทธิภาพ และให้สภาพแวดล้อมฐานข้อมูลที่สะอาดสำหรับแต่ละการทดสอบ ใช้ TestCase
เมื่อ:
- คุณกำลังทดสอบโมเดล มุมมอง หรือส่วนประกอบอื่นๆ ของแอปพลิเคชันของคุณ
- คุณต้องการให้แน่ใจว่าการทดสอบของคุณแยกจากกันและไม่รบกวนซึ่งกันและกัน
- คุณไม่จำเป็นต้องทดสอบการโต้ตอบกับฐานข้อมูลที่ซับซ้อนซึ่งครอบคลุมหลายธุรกรรม
TransactionTestCase: การทดสอบการโต้ตอบกับฐานข้อมูลที่ซับซ้อน
TransactionTestCase
เป็นอีกคลาสหนึ่งสำหรับการเขียนการทดสอบใน Django แต่แตกต่างจาก TestCase
ในวิธีการจัดการธุรกรรมฐานข้อมูล แทนที่จะย้อนกลับธุรกรรมหลังจากแต่ละวิธีการทดสอบ TransactionTestCase
จะคอมมิตธุรกรรม วิธีนี้ทำให้เหมาะสำหรับการทดสอบการโต้ตอบกับฐานข้อมูลที่ซับซ้อนซึ่งครอบคลุมหลายธุรกรรม เช่น การโต้ตอบที่เกี่ยวข้องกับสัญญาณหรือธุรกรรมอะตอมมิก
TransactionTestCase ทำงานอย่างไร
เมื่อคุณใช้ TransactionTestCase
Django จะดำเนินการตามขั้นตอนต่อไปนี้สำหรับแต่ละกรณีทดสอบ:
- สร้างฐานข้อมูลทดสอบ: Django สร้างฐานข้อมูลทดสอบแยกต่างหากสำหรับแต่ละการรันการทดสอบ
- ไม่ล้างฐานข้อมูล: TransactionTestCase *ไม่* ล้างฐานข้อมูลโดยอัตโนมัติก่อนแต่ละการทดสอบ คาดว่าฐานข้อมูลจะอยู่ในสถานะที่สอดคล้องกันก่อนที่จะรันแต่ละการทดสอบ
- รันวิธีการทดสอบ: Django ดำเนินการวิธีการทดสอบที่คุณกำหนดไว้
- คอมมิตธุรกรรม: หลังจากแต่ละวิธีการทดสอบ Django จะคอมมิตธุรกรรม ทำให้การเปลี่ยนแปลงถาวรในฐานข้อมูลทดสอบ
- ตัดทอนตาราง: เมื่อ *สิ้นสุด* การทดสอบทั้งหมดใน TransactionTestCase ตารางจะถูกตัดทอนเพื่อล้างข้อมูล
เนื่องจาก TransactionTestCase
คอมมิตธุรกรรมหลังจากแต่ละวิธีการทดสอบ จึงเป็นสิ่งสำคัญเพื่อให้แน่ใจว่าการทดสอบของคุณจะไม่ทิ้งฐานข้อมูลไว้ในสถานะที่ไม่สอดคล้องกัน คุณอาจต้องล้างข้อมูลใดๆ ที่สร้างขึ้นระหว่างการทดสอบด้วยตนเองเพื่อหลีกเลี่ยงการรบกวนการทดสอบครั้งต่อๆ ไป
ตัวอย่าง: การทดสอบสัญญาณ
ลองพิจารณาตัวอย่างของการทดสอบสัญญาณ Django โดยใช้ TransactionTestCase
:
from django.test import TransactionTestCase
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Product, ProductLog
@receiver(post_save, sender=Product)
def create_product_log(sender, instance, created, **kwargs):
if created:
ProductLog.objects.create(product=instance, action="Created")
class ProductSignalTest(TransactionTestCase):
def test_product_creation_signal(self):
product = Product.objects.create(name="Test Product", price=10.00)
self.assertEqual(ProductLog.objects.count(), 1)
self.assertEqual(ProductLog.objects.first().product, product)
self.assertEqual(ProductLog.objects.first().action, "Created")
ในตัวอย่างนี้ เรากำลังทดสอบสัญญาณที่สร้างอินสแตนซ์ ProductLog
เมื่อใดก็ตามที่สร้างอินสแตนซ์ Product
ใหม่ วิธีการ test_product_creation_signal
สร้างผลิตภัณฑ์ใหม่ จากนั้นตรวจสอบว่ามีการสร้างรายการบันทึกผลิตภัณฑ์ที่สอดคล้องกัน
ควรใช้ TransactionTestCase เมื่อใด
โดยทั่วไปแล้ว TransactionTestCase
จะใช้ในสถานการณ์เฉพาะที่คุณต้องทดสอบการโต้ตอบกับฐานข้อมูลที่ซับซ้อนซึ่งครอบคลุมหลายธุรกรรม พิจารณาใช้ TransactionTestCase
เมื่อ:
- คุณกำลังทดสอบสัญญาณที่ถูกทริกเกอร์โดยการดำเนินการฐานข้อมูล
- คุณกำลังทดสอบธุรกรรมอะตอมมิกที่เกี่ยวข้องกับการดำเนินการฐานข้อมูลหลายรายการ
- คุณต้องตรวจสอบสถานะของฐานข้อมูลหลังจากชุดของการดำเนินการที่เกี่ยวข้อง
- คุณกำลังใช้โค้ดที่อาศัย ID ที่เพิ่มขึ้นอัตโนมัติเพื่อคงอยู่ระหว่างการทดสอบ (แม้ว่าโดยทั่วไปจะถือว่าเป็นแนวทางปฏิบัติที่ไม่ดี)
ข้อควรพิจารณาที่สำคัญเมื่อใช้ TransactionTestCase
เนื่องจาก TransactionTestCase
คอมมิตธุรกรรม จึงเป็นสิ่งสำคัญที่ต้องตระหนักถึงข้อควรพิจารณาต่อไปนี้:
- การล้างฐานข้อมูล: คุณอาจต้องล้างข้อมูลใดๆ ที่สร้างขึ้นระหว่างการทดสอบด้วยตนเองเพื่อหลีกเลี่ยงการรบกวนการทดสอบครั้งต่อๆ ไป พิจารณาใช้วิธีการ
setUp
และtearDown
เพื่อจัดการข้อมูลทดสอบ - การแยกการทดสอบ:
TransactionTestCase
ไม่ได้ให้ระดับการแยกการทดสอบเช่นเดียวกับTestCase
พึงระลึกถึงการโต้ตอบที่อาจเกิดขึ้นระหว่างการทดสอบ และตรวจสอบให้แน่ใจว่าการทดสอบของคุณไม่ได้อาศัยสถานะของฐานข้อมูลจากการทดสอบก่อนหน้า - ประสิทธิภาพ:
TransactionTestCase
อาจช้ากว่าTestCase
เพราะเกี่ยวข้องกับการคอมมิตธุรกรรม ใช้อย่างรอบคอบและเฉพาะเมื่อจำเป็น
แนวทางปฏิบัติที่ดีที่สุดสำหรับการทดสอบ Django
ต่อไปนี้เป็นแนวทางปฏิบัติที่ดีที่สุดบางประการที่ควรคำนึงถึงเมื่อเขียนการทดสอบใน Django:
- เขียนการทดสอบที่ชัดเจนและกระชับ: การทดสอบควรง่ายต่อการทำความเข้าใจและบำรุงรักษา ใช้ชื่อที่สื่อความหมายสำหรับวิธีการทดสอบและการยืนยัน
- ทดสอบทีละอย่าง: แต่ละวิธีการทดสอบควรมุ่งเน้นไปที่การทดสอบลักษณะเดียวของโค้ดของคุณ วิธีนี้ทำให้ง่ายต่อการระบุแหล่งที่มาของความล้มเหลวเมื่อการทดสอบล้มเหลว
- ใช้การยืนยันที่มีความหมาย: ใช้วิธีการยืนยันที่แสดงลักษณะการทำงานที่คาดหวังของโค้ดของคุณอย่างชัดเจน Django มีชุดวิธีการยืนยันมากมายสำหรับสถานการณ์ต่างๆ
- ทำตามรูปแบบ Arrange-Act-Assert: จัดโครงสร้างการทดสอบของคุณตามรูปแบบ Arrange-Act-Assert: จัดเตรียมข้อมูลทดสอบ ดำเนินการกับโค้ดภายใต้การทดสอบ และยืนยันผลลัพธ์ที่คาดหวัง
- ทำให้การทดสอบของคุณรวดเร็ว: การทดสอบที่ช้าอาจทำให้ผู้พัฒนาท้อแท้จากการรันบ่อยๆ เพิ่มประสิทธิภาพการทดสอบของคุณเพื่อลดเวลาดำเนินการให้เหลือน้อยที่สุด
- ใช้ฟิกเจอร์สำหรับข้อมูลทดสอบ: ฟิกเจอร์เป็นวิธีที่สะดวกในการโหลดข้อมูลเริ่มต้นลงในฐานข้อมูลทดสอบของคุณ ใช้ฟิกเจอร์เพื่อสร้างข้อมูลทดสอบที่สอดคล้องกันและนำกลับมาใช้ใหม่ได้ พิจารณาใช้คีย์ธรรมชาติในฟิกเจอร์เพื่อหลีกเลี่ยงการฮาร์ดโค้ด ID
- พิจารณาใช้ไลบรารีการทดสอบเช่น pytest: แม้ว่าเฟรมเวิร์กการทดสอบในตัวของ Django จะมีประสิทธิภาพ แต่ไลบรารีเช่น pytest สามารถนำเสนอคุณสมบัติและความยืดหยุ่นเพิ่มเติมได้
- มุ่งมั่นเพื่อให้ครอบคลุมการทดสอบสูง: ตั้งเป้าหมายเพื่อให้ครอบคลุมการทดสอบสูงเพื่อให้แน่ใจว่าโค้ดของคุณได้รับการทดสอบอย่างละเอียด ใช้เครื่องมือครอบคลุมเพื่อวัดความครอบคลุมการทดสอบของคุณและระบุพื้นที่ที่ต้องการการทดสอบเพิ่มเติม
- รวมการทดสอบเข้ากับไปป์ไลน์ CI/CD ของคุณ: รันการทดสอบของคุณโดยอัตโนมัติเป็นส่วนหนึ่งของไปป์ไลน์การรวมอย่างต่อเนื่องและการปรับใช้ต่อเนื่อง (CI/CD) ของคุณ วิธีนี้ทำให้มั่นใจได้ว่าการถดถอยใดๆ จะถูกตรวจพบตั้งแต่เนิ่นๆ ในกระบวนการพัฒนา
- เขียนการทดสอบที่สะท้อนถึงสถานการณ์จริง: ทดสอบแอปพลิเคชันของคุณในลักษณะที่เลียนแบบวิธีที่ผู้ใช้จะโต้ตอบกับแอปพลิเคชันจริง วิธีนี้จะช่วยให้คุณค้นพบข้อบกพร่องที่อาจไม่ปรากฏในการทดสอบหน่วยอย่างง่าย ตัวอย่างเช่น พิจารณาความแตกต่างในที่อยู่และหมายเลขโทรศัพท์ระหว่างประเทศเมื่อทดสอบแบบฟอร์ม
การทำให้เป็นสากล (i18n) และการทดสอบ
เมื่อพัฒนาแอปพลิเคชัน Django สำหรับผู้ชมทั่วโลก สิ่งสำคัญคือต้องพิจารณาการทำให้เป็นสากล (i18n) และการแปลเป็นภาษาท้องถิ่น (l10n) ตรวจสอบให้แน่ใจว่าการทดสอบของคุณครอบคลุมภาษา รูปแบบวันที่ และสัญลักษณ์สกุลเงินที่แตกต่างกัน นี่คือเคล็ดลับบางประการ:
- ทดสอบด้วยการตั้งค่าภาษาที่แตกต่างกัน: ใช้ตัวตกแต่ง
override_settings
ของ Django เพื่อทดสอบแอปพลิเคชันของคุณด้วยการตั้งค่าภาษาที่แตกต่างกัน - ใช้ข้อมูลที่เป็นภาษาท้องถิ่นในการทดสอบของคุณ: ใช้ข้อมูลที่เป็นภาษาท้องถิ่นในฟิกเจอร์การทดสอบและวิธีการทดสอบของคุณเพื่อให้แน่ใจว่าแอปพลิเคชันของคุณจัดการรูปแบบวันที่ สัญลักษณ์สกุลเงิน และข้อมูลเฉพาะภาษาอื่นๆ ได้อย่างถูกต้อง
- ทดสอบสตริงการแปลของคุณ: ตรวจสอบว่าสตริงการแปลของคุณได้รับการแปลอย่างถูกต้องและแสดงผลอย่างถูกต้องในภาษาต่างๆ
- ใช้แท็กเทมเพลต
localize
: ในเทมเพลตของคุณ ให้ใช้แท็กเทมเพลตlocalize
เพื่อจัดรูปแบบวันที่ ตัวเลข และข้อมูลเฉพาะภาษาอื่นๆ ตามภาษาปัจจุบันของผู้ใช้
ตัวอย่าง: การทดสอบด้วยการตั้งค่าภาษาที่แตกต่างกัน
from django.test import TestCase
from django.utils import translation
from django.conf import settings
class InternationalizationTest(TestCase):
def test_localized_date_format(self):
original_language = translation.get_language()
try:
translation.activate('de') # เปิดใช้งานภาษาเยอรมัน
with self.settings(LANGUAGE_CODE='de'): # ตั้งค่าภาษาในการตั้งค่า
from django.utils import formats
from datetime import date
d = date(2024, 1, 20)
formatted_date = formats.date_format(d, 'SHORT_DATE_FORMAT')
self.assertEqual(formatted_date, '20.01.2024')
finally:
translation.activate(original_language) # คืนค่าภาษาเดิม
ตัวอย่างนี้สาธิตวิธีการทดสอบการจัดรูปแบบวันที่ด้วยการตั้งค่าภาษาที่แตกต่างกันโดยใช้โมดูล translation
และ formats
ของ Django
สรุป
การทำความเข้าใจความแตกต่างระหว่าง TestCase
และ TransactionTestCase
เป็นสิ่งสำคัญสำหรับการเขียนการทดสอบที่มีประสิทธิภาพและน่าเชื่อถือใน Django โดยทั่วไปแล้ว TestCase
เป็นตัวเลือกที่ต้องการสำหรับสถานการณ์การทดสอบส่วนใหญ่ ซึ่งเป็นวิธีที่รวดเร็วและมีประสิทธิภาพในการทดสอบส่วนประกอบแต่ละส่วนของแอปพลิเคชันของคุณแยกกัน TransactionTestCase
มีประโยชน์สำหรับการทดสอบการโต้ตอบกับฐานข้อมูลที่ซับซ้อนซึ่งครอบคลุมหลายธุรกรรม เช่น การโต้ตอบที่เกี่ยวข้องกับสัญญาณหรือธุรกรรมอะตอมมิก การทำตามแนวทางปฏิบัติที่ดีที่สุดและการพิจารณาด้านการทำให้เป็นสากล คุณสามารถสร้างชุดการทดสอบที่แข็งแกร่งซึ่งรับประกันคุณภาพและความสามารถในการบำรุงรักษาของแอปพลิเคชัน Django ของคุณ