ข้ามไปยังเนื้อหา

Testing & Tooling

ภาษาส่วนใหญ่ต้องการ testing framework จาก third-party แต่ Go มีทุกอย่างที่จำเป็นอยู่ใน standard library แล้ว package testing มี *testing.T สำหรับ unit tests, *testing.B สำหรับ benchmarks, และ *testing.F สำหรับ fuzzing — ทั้งหมดขับเคลื่อนด้วยคำสั่งเดียว: go test

go toolchain ที่ติดตั้งมาพร้อมกับ Go ทุกเวอร์ชันยังรวมถึง:

เครื่องมือวัตถุประสงค์
go testรัน tests และ benchmarks
go buildcompile packages และ binary
go runcompile และรันในขั้นตอนเดียว
go fmtจัดรูปแบบ source code (เรียกใช้ gofmt)
go vetstatic analysis สำหรับข้อผิดพลาดทั่วไป
go modจัดการ module และ dependency
go docเรียกดู package documentation
  1. go-test — การเขียนไฟล์ _test.go, func TestXxx(t *testing.T), table-driven tests ด้วย t.Run, t.Parallel, fuzzing ด้วย *testing.F, และทุกอย่างที่ *testing.T มีให้
  2. benchmarks — การวัด performance ด้วย func BenchmarkXxx(b *testing.B), การอ่านผลลัพธ์จาก -benchmem, และการเปรียบเทียบผลรันด้วย benchstat
  3. modules — การจัดการ dependency ด้วย go mod init, go.mod, go.sum, go get, และวิธีที่ version resolution ทำงานจริง ๆ
  4. go-tooling — คำสั่งประจำวัน: go run, go build, go fmt, analyzer จับ bug ของ go vet, go doc, และ ecosystem ของ linter ที่กว้างขึ้น
flowchart TD
  T["testing package"] --> A["*testing.T — unit tests, table-driven, t.Parallel"]
  T --> B["*testing.B — benchmarks, b.Loop, benchstat"]
  T --> F["*testing.F — fuzzing, mutates inputs to find edge cases"]
  A --> G["go test ./..."]
  B --> G
  F --> G
package เดียว คำสั่งเดียว สามวิธีในการทดสอบโค้ดของคุณ

วิธีที่ดีที่สุดในการทำความเข้าใจ testing tools ของ Go คือเริ่มต้นด้วย pure function — function ที่ไม่มี side effect ไม่มี I/O มีแค่ input และ output Playground ด้านล่างรัน Fibonacci และ IsPrime จาก main ในบทเรียนถัดไปคุณจะเขียนไฟล์ _test.go สำหรับ function แบบนี้โดยตรง — รวมถึง fuzz test ด้วย เพราะ Fibonacci และ IsPrime ต่างรับ int ตัวเดียว ตรงตามรูปแบบที่ *testing.F ถนัดในการโหม input แบบ edge-case ใส่

package main
import "fmt"
// Fibonacci returns the nth Fibonacci number (0-indexed).
// fib(0)=0, fib(1)=1, fib(2)=1, fib(3)=2, ...
func Fibonacci(n int) int {
if n <= 1 {
return n
}
return Fibonacci(n-1) + Fibonacci(n-2)
}
// IsPrime reports whether n is a prime number.
func IsPrime(n int) bool {
if n < 2 {
return false
}
for i := 2; i*i <= n; i++ {
if n%i == 0 {
return false
}
}
return true
}
func main() {
// Print the first 10 Fibonacci numbers
fmt.Println("Fibonacci sequence:")
for i := 0; i < 10; i++ {
fmt.Printf(" fib(%d) = %d\n", i, Fibonacci(i))
}
// Find primes up to 30
fmt.Println("Primes up to 30:")
for n := 2; n <= 30; n++ {
if IsPrime(n) {
fmt.Printf(" %d\n", n)
}
}
}
สิ่งที่ได้ประโยชน์ต้นทุน
built-in testing packageไม่ต้อง install, standard API ทุก Go projectไม่มี assertion library — ต้องเขียน if got != want เอง
table-driven teststest หลาย case ใน 1 function, เพิ่ม case ได้ง่ายถ้า table ยาวมาก อ่านยาก
go test ./...รัน test ทุก package ใน module ครั้งเดียวparallel test อาจ race ถ้าไม่ระวัง shared state
race detector (-race)ตรวจหา concurrent bugs อัตโนมัติช้าลง 2-10x — ควรรันใน CI ไม่ใช่ทุก local run
  • ต้องใช้ testing framework เหมือน Python/Node — Go standard library มีทุกอย่างที่ต้องการ ไม่ต้องติดตั้ง testify หรือ gomega เพิ่ม
  • coverage 100% = code ปลอดภัย — coverage วัดว่า line ถูกรันหรือไม่ ไม่ได้วัด correctness — test ที่ไม่มี assertion ก็ทำให้ coverage 100% ได้
  • go test ช้า — go test cache ผลลัพธ์ — ถ้าไม่มีไฟล์เปลี่ยน จะ return cached result ทันที

ก่อนจะลงลึกในโมดูลนี้ เริ่มจาก failure mode ที่พบบ่อยที่สุดของ test ที่ generate มา: test ที่รันแล้วพิมพ์ PASS แต่ไม่เช็กอะไรเลย

  • ทุก function Test* เรียก t.Error/t.Errorf/t.Fatal/t.Fatalf ที่ไหนสักแห่งจริงไหม หรือแค่เรียก function ที่ทดสอบแล้วปล่อยให้ nil error หมายถึง success แบบเงียบ ๆ?
  • ถ้า function คืนค่ากลับมา ค่านั้นถูกเทียบกับผลลัพธ์ที่คาดหวังจริงไหม — หรือแค่เช็กว่า “ไม่ panic”?
  • assertion logic ของ benchmark หรือ fuzz test ขึ้นอยู่กับ input จริงไหม หรือจะ pass เหมือนเดิมไม่ว่า function ที่ทดสอบจะคืนอะไรมา?

🐛 หา bug ในโค้ดนี้

func TestParseConfig(t *testing.T) {
cfg := ParseConfig("timeout=30")
fmt.Println(cfg) // just prints — never compares to anything
}
เฉลย

test นี้เรียก ParseConfig พิมพ์ผลลัพธ์ แล้วจบ — ไม่มี t.Error, t.Errorf, t.Fatal หรือการเทียบกับค่าที่คาดหวังที่ไหนเลย go test รายงาน PASS สำหรับ test นี้เสมอ ไม่ว่า ParseConfig จะคืน struct ที่ถูกต้อง คืน struct ว่าง หรือทำ timeout field หายไปเงียบ ๆ ก็ตาม โค้ดนี้ compile ผ่านสะอาดและ “ดูเหมือน test” — ซึ่งนั่นแหละคืออันตราย: bug จริงที่เกิดขึ้นทีหลังจะไม่ทำให้ test นี้แดงเลย เพราะไม่มีอะไรในนั้นที่ fail ได้ วิธีแก้คือ assertion จริง: want := Config{Timeout: 30 * time.Second}; if cfg != want { t.Errorf("ParseConfig() = %+v, want %+v", cfg, want) }

คำสั่งใดรัน tests ทั้งหมดใน module ปัจจุบันและทุก sub-package?
package ใดใน Go standard library ที่ให้ *testing.T, *testing.B และ *testing.F?
อะไรทำให้ function เป็น candidate ที่ดีสำหรับ unit test แรก?
go test จะรายงาน PASS หรือ FAIL สำหรับ test นี้ ถ้า ParseConfig มี bug ที่คืน zero-value Config?
func TestParseConfig(t *testing.T) {
    cfg := ParseConfig("timeout=30")
    fmt.Println(cfg)
}
เครื่องมือใดจัดรูปแบบ Go source code ตาม canonical style?
บทเรียนของโมดูลนี้ครอบคลุม testing.T, testing.B และ testing.F บทไหนสอนเรื่อง fuzzing?