Testing & Tooling
เรื่องราวของ testing ใน Go
หัวข้อที่มีชื่อว่า “เรื่องราวของ testing ใน Go”ภาษาส่วนใหญ่ต้องการ 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 build | compile packages และ binary |
go run | compile และรันในขั้นตอนเดียว |
go fmt | จัดรูปแบบ source code (เรียกใช้ gofmt) |
go vet | static analysis สำหรับข้อผิดพลาดทั่วไป |
go mod | จัดการ module และ dependency |
go doc | เรียกดู package documentation |
สิ่งที่โมดูลนี้ครอบคลุม
หัวข้อที่มีชื่อว่า “สิ่งที่โมดูลนี้ครอบคลุม”- go-test — การเขียนไฟล์
_test.go,func TestXxx(t *testing.T), table-driven tests ด้วยt.Run,t.Parallel, fuzzing ด้วย*testing.F, และทุกอย่างที่*testing.Tมีให้ - benchmarks — การวัด performance ด้วย
func BenchmarkXxx(b *testing.B), การอ่านผลลัพธ์จาก-benchmem, และการเปรียบเทียบผลรันด้วยbenchstat - modules — การจัดการ dependency ด้วย
go mod init,go.mod,go.sum,go get, และวิธีที่ version resolution ทำงานจริง ๆ - go-tooling — คำสั่งประจำวัน:
go run,go build,go fmt, analyzer จับ bug ของgo vet,go doc, และ ecosystem ของ linter ที่กว้างขึ้น
Family ของ testing package
หัวข้อที่มีชื่อว่า “Family ของ testing package”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
Function ที่คุ้มค่าแก่การทดสอบ
หัวข้อที่มีชื่อว่า “Function ที่คุ้มค่าแก่การทดสอบ”วิธีที่ดีที่สุดในการทำความเข้าใจ 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 tests | test หลาย 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 testcache ผลลัพธ์ — ถ้าไม่มีไฟล์เปลี่ยน จะ return cached result ทันที
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”ก่อนจะลงลึกในโมดูลนี้ เริ่มจาก failure mode ที่พบบ่อยที่สุดของ test ที่ generate มา: test ที่รันแล้วพิมพ์ PASS แต่ไม่เช็กอะไรเลย
- ทุก function
Test*เรียกt.Error/t.Errorf/t.Fatal/t.Fatalfที่ไหนสักแห่งจริงไหม หรือแค่เรียก function ที่ทดสอบแล้วปล่อยให้nilerror หมายถึง 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) }