Table-Driven Tests
Jest it.each vs Go table-driven tests
หัวข้อที่มีชื่อว่า “Jest it.each vs Go table-driven tests”ใน Jest คุณหยิบ it.each หรือ describe.each มาใช้เมื่อต้องรัน assertion logic เดิมซ้ำกับ input หลาย ๆ ชุด โดยไม่ต้อง copy-paste ตัว test function ส่วน Go ไม่มี it.each มาให้ในตัว แต่ community ลงตัวกันที่ idiom เดียว คือ slice ของ anonymous struct (“table”) ที่วน loop อยู่ใน test function เดียว แล้วรันแต่ละ case เป็น subtest ผ่าน t.Run
หมายเหตุเรื่อง Playground:
testingpackage ต้องรันใน binary context ของgo testบล็อกโค้ดด้านล่างคือไฟล์ test จริง — รันบนเครื่องคุณด้วยgo test ./...
เปรียบเทียบ: ทดสอบฟังก์ชันเดิม
หัวข้อที่มีชื่อว่า “เปรียบเทียบ: ทดสอบฟังก์ชันเดิม”// Jest — it.eachfunction add(a: number, b: number) { return a + b; }
it.each([ [1, 2, 3], [0, 0, 0], [-1, 1, 0], [10, 20, 30],])('add(%i, %i) = %i', (a, b, want) => { expect(add(a, b)).toBe(want);});// Go — table-driven ด้วย t.Runfunc add(a, b int) int { return a + b }
func TestAdd(t *testing.T) { tests := []struct { name string a, b int want int }{ {"positive", 1, 2, 3}, {"zeros", 0, 0, 0}, {"negative", -1, 1, 0}, {"large", 10, 20, 30}, }
for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got := add(tc.a, tc.b) if got != tc.want { t.Errorf("add(%d, %d) = %d; want %d", tc.a, tc.b, got, tc.want) } }) }}กายวิภาคของ table-driven test
หัวข้อที่มีชื่อว่า “กายวิภาคของ table-driven test”package math_test
import "testing"
func TestDivide(t *testing.T) { // 1. Table — slice ของ anonymous structs tests := []struct { name string a, b float64 want float64 wantErr bool }{ {"normal", 10, 2, 5, false}, {"fraction", 7, 2, 3.5, false}, {"divide_by_zero", 1, 0, 0, true}, }
// 2. วนลูปใน table for _, tc := range tests { // 3. t.Run สร้าง named subtest t.Run(tc.name, func(t *testing.T) { // 4. t.Parallel() — optional, รัน subtests พร้อมกัน t.Parallel()
got, err := Divide(tc.a, tc.b)
if (err != nil) != tc.wantErr { t.Fatalf("Divide() error = %v, wantErr %v", err, tc.wantErr) } if !tc.wantErr && got != tc.want { t.Errorf("Divide() = %v; want %v", got, tc.want) } }) }}convention สำคัญ:
t.Errorfทำเครื่องหมายว่า fail แต่ test ยังรันต่อt.Fatalfทำเครื่องหมายว่า fail แล้วหยุด subtest ปัจจุบันทันทีt.Parallel()ภายในt.Runให้ subtest รันพร้อมกันได้ (ปลอดภัยเมื่อแต่ละ case เป็นอิสระต่อกัน)- field
nameของแต่ละ case จะกลายเป็น label ของ subtest (TestDivide/normal,TestDivide/divide_by_zero)
รัน specific subtests
หัวข้อที่มีชื่อว่า “รัน specific subtests”# รัน tests ทั้งหมดgo test ./...
# รันเฉพาะ subtest ที่มีชื่อ (รองรับ regex)go test -run TestDivide/divide_by_zero ./...
# รัน subtests ทั้งหมดที่ชื่อมี "zero"go test -run TestDivide/.*zero.* ./...
# Verbose output — แสดงชื่อ subtest แต่ละตัวและ PASS/FAILgo test -v ./...เปรียบเทียบโครงสร้างกับ Jest
หัวข้อที่มีชื่อว่า “เปรียบเทียบโครงสร้างกับ Jest”// Jestdescribe('Divide', () => { it.each([ ['normal', 10, 2, 5], ['fraction', 7, 2, 3.5], ])('%s', (_, a, b, want) => { expect(divide(a, b)).toBe(want); });
it('throws on divide by zero', () => { expect(() => divide(1, 0)).toThrow(); });});// Gofunc TestDivide(t *testing.T) { tests := []struct { name string a, b float64 want float64 wantErr bool }{ {"normal", 10, 2, 5, false}, {"fraction", 7, 2, 3.5, false}, {"by_zero", 1, 0, 0, true}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got, err := Divide(tc.a, tc.b) if (err != nil) != tc.wantErr { t.Fatal(...) } if !tc.wantErr && got != tc.want { t.Errorf(...) } }) }}