Back to notes
mastery-backend-go
Featured

Go Testing Mastery: พลังของ Table Driven Tests & Mocking

เขียน Test ใน Go ต้องไม่งง! เจาะลึกมาตรฐานการทำ Table Driven Tests และการใช้ Interface เพื่อทำ Mocking ขั้นเทพที่ทำให้คุณ Test โค้ดได้ 100%

January 30, 20262 min read readNNexis by Seereen

🛑 1. The Problem First: "โค้ดวนลูป... แต่รัน Test ทีละข้อ"

ลองนึกถึงวันที่คุณต้องเขียน Unit Test ให้กับฟังก์ชันคำนวณภาษีที่มีเคสยิบย่อย 20 แบบ:

HLJS GO
// ❌ Naive Approach: เขียนฟังก์ชัน Test แยกกันทุุกเคส
func TestTaxSmallIncome(t *testing.T) { ... }
func TestTaxMediumIncome(t *testing.T) { ... }
func TestTaxHighIncome(t *testing.T) { ... }
// 🌋 พัง! โค้ด Test จะยาวเหยียดและเต็มไปด้วยการ Copy-Paste (Boilerplate)
// ถ้าวันหนึ่งคุณเปลี่ยนโครงสร้างข้อมูล คุณต้องไล่ตามแก้โค้ด Test ทั้ง 20 ฟังก์ชัน!

ปัญหา: การเขียน Test แบบกระจัดกระจายทำให้บำรุงรักษาได้ยากมาก (Low Maintainability) และ Go-Style ไม่ต้องการให้เราเสียเวลากับความพยายามที่ไม่จำเป็น เราต้องการวิธีที่ "เพิ่มเคสใหม่ได้แค่บรรทัดเดียว" ครับ


💡 2. Real-Life Analogy: การตรวจสอบคุณภาพสินค้า (QA Checklist)

  • Table Driven Tests: เหมือน "ตารางเช็คลิสต์บนกระดาน". คุณมีเครื่องจักร (Function) เครื่องเดิม แต่คุณแค่เปลี่ยนวัตถุดิบ (Input) และตรวจเช็คผลลัพธ์ (Expected) ตามตารางที่วางไว้ ไม่ต้องสร้างเครื่องจักรใหม่ทุุกครั้งที่อยากตรวจของ
  • Interface Mocking: เหมือน "หุ่นยนต์ลองชุด". แทนที่จะเอาคนจริงๆ (Database จริง) มายืนให้คุณลองเสื้อ (Test Service) คุณแค่ใช้หุ่นยนต์ที่มีรูปร่างเหมือนคน (Interface) มาใส่ชุดแทน คุณจะทำอะไรกับหุ่นยนต์ก็ได้ ไม่ต้องกลัวคนจริงๆ จะเจ็บ (ข้อมูลจริงจะเลอะ)

🚀 3. Execution Journey: มหากาพย์การสร้างตารางทดสอบ

มาตรฐานของ Go คือการใช้ Anonymous Struct Slice เพื่อทำตารางทดสอบ

🛠 Step-by-step:

  1. The Table: สร้าง slice ของ struct ที่บรรจุ Input, Expected และชื่อของ Test Case
  2. The Loop: ใช้ for range วนลูปผ่านทุุกบรรทัดในตาราง
  3. The Sub-test: ใช้ t.Run() เพื่อแยกผลการรันของแต่ละเคสให้ชัดเจน
  4. The Interface: ออกแบบ Service ให้รับ Interface เพื่อให้เราสามารถส่ง "ของปลอม" เข้าไปในรอบการทดสอบได้
HLJS GO
// ✅ Best Practice: Table Driven Test (มาตรฐาน Go)
func TestCalculateTax(t *testing.T) {
	tests := []struct {
		name     string
		income   float64
		expected float64
	}{
		{"Low", 100, 0},        // 🛠 เพิ่มเคสง่ายๆ แค่บรรทัดเดียว
		{"High", 1000, 100},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := CalculateTax(tt.income); got != tt.expected {
				t.Errorf("got %v, want %v", got, tt.expected)
			}
		})
	}
}

🪤 4. The Junior Trap: โรค "Assertion Library Addiction"

จูเนียร์ที่เคยชินกับ JS หรือ Java มักจะพยายามหา Library มาทำ assert.Equal(a, b):

HLJS GO
// ❌ Junior Trap: พึ่งพา External Library มากเกินไป
assert.Equal(t, 100, result) // 🌋 พัง! เมื่อมี Error คุณจะไม่ได้รับคำอธิบายที่ชัดเจน (Custom Message)
// และทีมงานคนอื่นต้องมาตกลงกันว่าจะใช้ Lib ตัวไหน (เพิ่ม Dependency ไม่จำเป็น)

ระวัง: Go เน้นใช้ Standard Lib if got != tt.expected { t.Errorf(...) } เพื่อความเรียบง่าย ✅ การแก้ไข: ฝึกเขียนแบบ Go Native เพื่อให้โค้ดของคุณพกพาได้ง่าย (Portable) และทุุกคนในโลก Go อ่านรู้เรื่องทันทีครับ


⚖️ 5. The Why Matrix: Manual Test vs Table Driven

หัวข้อแยกฟังก์ชัน TestTable Driven (Senior)
การเพิ่มเคสใหม่🐢 ช้า (ต้องสร้างฟังก์ชันใหม่)⚡⚡⚡ เร็ว (เพิ่มแค่บรรทัดเดียว)
ความสะอาดของโค้ดรก (Copy-Paste)⚡⚡⚡ คลีน (Logic เดียวกัน)
การ Debugต้องหาไฟล์แยก⚡⚡ หาเจอในตารางเดียว
เหมาะกับงานที่ Logic คนละเรื่องกัน🚀 ฟังก์ชันเดียวที่เคสเยอะ (Business Logic)

🎓 6. Senior Mindset Summary

การเป็น Senior คือการมองว่า "เราไม่ได้เขียน Test เพื่อความสบายใจในวันนี้ แต่เพื่อความปลอดภัยในวันหน้า". การทำ Table Driven Test คือการทำระบบที่ 'งอก' เคสการทดสอบได้เร็วกว่าบั๊กที่งอกออกมาครับ!

Share this note

Mission Accomplished

You've reached the end of this module. Time to apply these senior mindsets to your real-world projects!

Explore more topics

© 2026 My Notes by Seereen