Lesson 37 — Advanced Testing (Visualization)

Lesson này dạy 3 kỹ thuật khó "tưởng tượng" nhất: fuzz testing (máy sinh input tìm bug), race detector (phát hiện data race giữa 2 goroutine), và golden file (snapshot test). Trang này minh hoạ trực quan từng cái — bạn có thể "chạy thử" trong browser mà không cần cài Go.

Module 1 — Fuzz Finder: máy tự tìm bug

Mục tiêu: test hàm Reverse(s) với invariant Reverse(Reverse(s)) == s. Phía dưới đây mô phỏng fuzzer: bắt đầu từ seed corpus, mutation từng bước để sinh input mới, chạy invariant. Nếu fail → lưu vào testdata/fuzz/<hash>.

0
iterations
0
passed
0
crashes

Fuzz log

Code dưới test

// CỐ Ý có bug: đảo theo BYTE
func ReverseBuggy(s string) string {
    b := []byte(s)
    for i,j := 0,len(b)-1; i<j; i,j=i+1,j-1 {
        b[i], b[j] = b[j], b[i]
    }
    return string(b) // ✗ vỡ UTF-8 với rune >1 byte
}

// Invariant fuzz check:
//   ReverseBuggy(ReverseBuggy(s)) == s
//   utf8.ValidString(ReverseBuggy(s))

Module 2 — Race Detector: 2 goroutine 1 ô nhớ

2 goroutine cùng gọi counter.Inc() với c.n++ (compile ra 3 lệnh: LOAD, ADD, STORE). Nếu lịch CPU xen kẽ "xấu", 2 goroutine cùng đọc giá trị cũ → mỗi cái +1 → STORE đè nhau → mất 1 update. go test -race phát hiện điều này bằng cách track happens-before relation.

🔵 Goroutine A — Inc()

1. LOAD r1 ← n
2. r1 = r1 + 1
3. STORE n ← r1
r1 (local) = ?

🟢 Shared memory

counter.n 0
Đáng lẽ phải = 2 sau 2 lần Inc(). Theo dõi xem có mất update không.

🟠 Goroutine B — Inc()

1. LOAD r1 ← n
2. r1 = r1 + 1
3. STORE n ← r1
r1 (local) = ?

Fix bằng Mutex: c.mu.Lock(); c.n++; c.mu.Unlock() — chỉ 1 goroutine giữ lock tại 1 thời điểm → 3 lệnh LOAD/ADD/STORE chạy nguyên khối, không xen kẽ. Race detector sẽ không cảnh báo nữa.

Module 3 — Golden File Workflow

Render template HTML từ data → so byte-by-byte với file testdata/golden/report.golden. Khi template thay đổi, dùng cờ go test -update để regenerate file golden, review diff bằng mắt rồi commit.

Actual output (render bây giờ)

Golden file (đã commit)

Chưa chạy test.

Diff