เรียน Go ภาษาง่าย 📘

🧪

เจาะลึก Testing + HTTP + JSON ใหม่ ฉบับภาษาง่าย

table test, fuzz, synctest, JSON v2, HTTP

7 บท
10 ข้อสอบ
1. Table test + Race

เขียนเคสเป็นตาราง คุ้มสุด:

go
func TestAdd(t *testing.T) {
    tests := []struct{ a, b, want int }{{1,2,3},{0,0,0},{-1,1,0}}
    for _, tt := range tests {
        if got := Add(tt.a, tt.b); got != tt.want {
            t.Errorf("Add(%d,%d)=%d want %d", tt.a, tt.b, got, tt.want)
        }
    }
}
bash
go test ./...
go test -race ./... # ตรวจแย่งกันด้วย
go test -run TestAdd -v
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


2. Fuzz + Bench + Pprof
go
func FuzzParse(f *testing.F) {
    f.Add("1-2")
    f.Fuzz(func(t *testing.T, s string) { _, _ = Parse(s) }) // โยนขยะแล้วต้องไม่ panic
}
bash
go test -fuzz=FuzzParse -fuzztime=30s
go test -bench=. -benchmem
go tool pprof -http=:8080 cpu.out # 1.26 เปิดมาเป็น flame graph เลย
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


3. ของใหม่: Synctest + NewTestServer

Go 1.27 มี 2 ของคู่กัน:

  • httptest.NewTestServer = เซิร์ฟเวอร์ปลอมใน memory ไม่กินพอร์ตจริง
  • testing/synctest = ห้องจำลองเวลาสำหรับเทสต์ concurrent ไม่ต้อง sleep จริง ลด flaky
go
srv := httptest.NewTestServer(myHandler)
defer srv.Close()
resp, _ := http.Get(srv.URL)

synctest.Test(t, func(t *testing.T) {
    // รันโค้ด channel/time ในโลกจำลอง
})
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


4. JSON ใหม่ v2 + jsontext

ข่าวดี: Go 1.27 เอาไส้ json/v2 มาใส่ใน encoding/json เดิมแล้ว = โค้ดเดิมเร็วขึ้นโดยไม่แก้:

go
type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
b, _ := json.Marshal(User{"Ann", 20})

อยากซิ่ง/เข้มค่อยใช้:

  • encoding/json/v2 = ตั้งค่าได้ (deterministic, unknown fields)
  • encoding/json/jsontext = สตรีมทีละ token เหมาะไฟล์ยักษ์
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


5. HTTP handler รับ context + Mini Project

ทุก handler รับ ctx มาด้วยเพื่อ timeout/yกเลิก:

go
func userHandler(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
    defer cancel()
    // ... ใช้ ctx กับ DB / เรียกต่อ ...
    json.NewEncoder(w).Encode(user)
}

Mini: ทำ API /price?symbol=AAPL ดึงราคาปลอม (sleep สุ่ม) + timeout 500ms + table test + NewTestServer test + go test -race ต้องผ่าน

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ภาคผนวก: ตัวอย่างเพิ่ม + Best Practices
ตัวอย่างเพิ่ม 1: table test เต็มรูปแบบ
go
func TestAdd(t *testing.T) {
    tests := []struct {
        name   string
        a, b   int
        want   int
    }{
        {"บวกปกติ", 1, 2, 3},
        {"ติดลบ", -1, -2, -3},
        {"ศูนย์", 0, 0, 0},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Add(tt.a, tt.b); got != tt.want {
                t.Errorf("Add(%d,%d) = %d อยากได้ %d", tt.a, tt.b, got, tt.want)
            }
        })
    }
}
ตัวอย่างเพิ่ม 2: fuzz หาบั๊กที่คิดไม่ถึง
go
func FuzzReverse(f *testing.F) {
    f.Add("hello")
    f.Add("")
    f.Fuzz(func(t *testing.T, s string) {
        if got := Reverse(Reverse(s)); got != s {
            t.Errorf("กลับไปกลับมาไม่ได้: %q", s)
        }
    })
}
ตัวอย่างเพิ่ม 3: เทสต์ handler ไม่ต้องเปิด port จริง
go
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("ok"))
}))
defer srv.Close()
res, err := http.Get(srv.URL)
if err != nil || res.StatusCode != 200 {
    t.Fatal("server ไม่ตอบ")
}
Best practices เช็กลิสต์
  • table test เป็นโครงหลัก เคสใหม่ = เพิ่มแถวเดียว
  • ชื่อ subtest ภาษาคน (t.Run("ติดลบ", ...)) fail แล้วรู้เลยเคสไหน
  • รัน -race ทุกครั้งก่อน push
  • fuzz ใส่ seed ดีๆ (empty/ยาว/unicode) แล้วปล่อยสัก 30 วิ
  • เทสต์ handler ผ่าน httptest ไม่ต้องเปิด port จริง
  • แยก logic ออกจาก handler (pure func) เทสต์ง่ายกว่า 10 เท่า
  • external (DB/net) อยู่หลัง interface จะได้ stub ได้
แบบฝึกหัด
  • เขียน table test ให้ statusOf (จากเล่ม errors) ครบ 3 เคส
  • fuzz Reverse 1 นาทีแล้วแก้บั๊กที่เจอ

อ้างอิง: Go 1.27 blog (json/v2, NewTestServer, synctest), Go 1.26 blog (Green Tea GC, pprof flame default)