Chuyển tới nội dung chính

👅 Nếm Thử Món - Testing

Triết Lý Nhà Hàng

Đầu bếp giỏi luôn nếm thử món trước khi ra khách. Testing trong Go như việc kiểm tra chất lượng từng món ăn trước khi phục vụ!

1. Nếm Thử Là Gì? (What is Testing?)

Testing là quá trình kiểm tra xem code của bạn có hoạt động đúng không - giống như đầu bếp nếm thử món ăn trước khi đưa cho khách.

// Món ăn cần kiểm tra
package kitchen

func CookRice(cups int) int {
return cups * 2 // 1 chén gạo = 2 chén cơm
}

2. Viết Công Thức Nếm Thử (Writing Test Functions)

File test trong Go có đuôi _test.go và hàm test bắt đầu với Test.

// kitchen_test.go
package kitchen

import "testing"

// Công thức nếm thử cơm
func TestCookRice(t *testing.T) {
result := CookRice(3)
expected := 6

if result != expected {
t.Errorf("Món không đúng! Muốn %d chén, được %d chén", expected, result)
}
}

Cách chạy:

go test

3. Các Cách Báo Món Sai (Error Reporting Methods)

func TestDishQuality(t *testing.T) {
// t.Error - Báo lỗi nhưng test tiếp
if temperature < 100 {
t.Error("Món chưa nóng đủ!")
}

// t.Fatal - Báo lỗi và dừng ngay
if ingredient == nil {
t.Fatal("Thiếu nguyên liệu chính!")
}

// t.Errorf - Báo lỗi có format
if saltLevel != 5 {
t.Errorf("Độ mặn sai: muốn %d, được %d", 5, saltLevel)
}
}

4. Nếm Nhiều Món Cùng Lúc (Table-Driven Tests)

Kỹ thuật test nhiều trường hợp trong một lần - như nếm thử cả menu!

func TestCookMultipleDishes(t *testing.T) {
// Menu cần test
tests := []struct {
dishName string
cups int
want int
}{
{"Cơm ít", 1, 2},
{"Cơm vừa", 2, 4},
{"Cơm nhiều", 3, 6},
}

for _, tt := range tests {
t.Run(tt.dishName, func(t *testing.T) {
got := CookRice(tt.cups)
if got != tt.want {
t.Errorf("Món %s: muốn %d, được %d",
tt.dishName, tt.want, got)
}
})
}
}

Output khi chạy:

=== RUN   TestCookMultipleDishes
=== RUN TestCookMultipleDishes/Cơm_ít
=== RUN TestCookMultipleDishes/Cơm_vừa
=== RUN TestCookMultipleDishes/Cơm_nhiều
--- PASS: TestCookMultipleDishes (0.00s)

5. Test Món Phức Tạp (Testing Complex Functions)

package restaurant

type Dish struct {
Name string
Ingredients []string
Price float64
}

func PrepareDish(name string, budget float64) (*Dish, error) {
if budget < 50000 {
return nil, fmt.Errorf("ngân sách không đủ")
}

return &Dish{
Name: name,
Ingredients: []string{"thịt", "rau", "gia vị"},
Price: budget * 0.8,
}, nil
}

// Test món phức tạp
func TestPrepareDish(t *testing.T) {
dish, err := PrepareDish("Phở bò", 100000)

if err != nil {
t.Fatalf("Lỗi khi chuẩn bị món: %v", err)
}

if dish.Name != "Phở bò" {
t.Errorf("Tên món sai: %s", dish.Name)
}

if len(dish.Ingredients) == 0 {
t.Error("Món thiếu nguyên liệu!")
}

if dish.Price <= 0 {
t.Errorf("Giá món không hợp lệ: %.0f", dish.Price)
}
}

6. Đo Tốc Độ Nấu (Benchmarking)

Benchmark giúp đo tốc độ thực thi - như đo xem đầu bếp nấu nhanh hay chậm.

func BenchmarkCookRice(b *testing.B) {
for i := 0; i < b.N; i++ {
CookRice(5)
}
}

func BenchmarkPrepareFullMeal(b *testing.B) {
for i := 0; i < b.N; i++ {
PrepareDish("Cơm tấm", 150000)
}
}

Chạy benchmark:

go test -bench=.

Kết quả:

BenchmarkCookRice-8           100000000    10.5 ns/op
BenchmarkPrepareFullMeal-8 5000000 320 ns/op

7. Setup và Cleanup Bếp (Test Setup/Teardown)

func TestMain(m *testing.M) {
// Setup - Chuẩn bị bếp
fmt.Println("🔥 Khởi động bếp...")
setupKitchen()

// Chạy tất cả tests
code := m.Run()

// Cleanup - Dọn dẹp bếp
fmt.Println("🧹 Dọn dẹp bếp...")
cleanupKitchen()

os.Exit(code)
}

func setupKitchen() {
// Mở gas, chuẩn bị dụng cụ
}

func cleanupKitchen() {
// Tắt bếp, rửa chén
}

8. Test Ví Dụ Thực Tế (Real Example)

package restaurant

import "testing"

// Hàm tính tổng bill
func CalculateBill(dishes []Dish, discount float64) float64 {
total := 0.0
for _, dish := range dishes {
total += dish.Price
}
return total * (1 - discount)
}

// Test với nhiều case
func TestCalculateBill(t *testing.T) {
tests := []struct {
name string
dishes []Dish
discount float64
want float64
}{
{
name: "Không giảm giá",
dishes: []Dish{
{Name: "Phở", Price: 50000},
{Name: "Trà", Price: 10000},
},
discount: 0,
want: 60000,
},
{
name: "Giảm 10%",
dishes: []Dish{
{Name: "Cơm tấm", Price: 45000},
{Name: "Nước ngọt", Price: 15000},
},
discount: 0.1,
want: 54000,
},
{
name: "Không order món nào",
dishes: []Dish{},
discount: 0,
want: 0,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := CalculateBill(tt.dishes, tt.discount)
if got != tt.want {
t.Errorf("Bill sai: muốn %.0f, được %.0f", tt.want, got)
}
})
}
}

// Benchmark tính bill
func BenchmarkCalculateBill(b *testing.B) {
dishes := []Dish{
{Name: "Món 1", Price: 50000},
{Name: "Món 2", Price: 60000},
{Name: "Món 3", Price: 70000},
}

for i := 0; i < b.N; i++ {
CalculateBill(dishes, 0.1)
}
}

9. Mẹo Nếm Thử Chuyên Nghiệp (Testing Best Practices)

// ✅ TỐT - Test rõ ràng, dễ hiểu
func TestCookRice_WithValidInput(t *testing.T) {
got := CookRice(3)
want := 6

if got != want {
t.Errorf("CookRice(3) = %d; want %d", got, want)
}
}

// ❌ TỆ - Test không rõ ràng
func TestFunc(t *testing.T) {
if CookRice(3) != 6 {
t.Error("wrong")
}
}

// ✅ TỐT - Sử dụng subtests
func TestKitchen(t *testing.T) {
t.Run("nấu cơm", func(t *testing.T) {
// test nấu cơm
})

t.Run("nấu phở", func(t *testing.T) {
// test nấu phở
})
}

// ✅ TỐT - Test helper function
func assertDishQuality(t *testing.T, got, want Dish) {
t.Helper()
if got != want {
t.Errorf("Món không đúng:\n got: %+v\n want: %+v", got, want)
}
}
Lời Khuyên Từ Bếp Trưởng
  • 🧪 Viết test trước khi code (TDD - Test Driven Development)
  • 📊 Dùng table-driven tests cho nhiều case
  • ⚡ Benchmark để tối ưu hiệu suất
  • 🎯 Mỗi test nên test một việc cụ thể
  • 📝 Tên test phải mô tả rõ ràng

Bài tiếp theo: 🌐 Mở Nhà Hàng Trực Tuyến - Web Development

"Đầu bếp giỏi không chỉ nấu ngon, mà còn biết kiểm tra chất lượng!" - Triết lý Testing