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

Testing trong Go - Kiểm tra Code của Bạn! 🧪

Chào mừng bạn đến với bài học về Testing trong Go! Trong bài học này, chúng ta sẽ tìm hiểu cách viết và chạy các bài test để đảm bảo code của bạn hoạt động đúng.

Unit Testing 🎯

1. Test Cơ bản

func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) = %d; mong đợi %d", result, expected)
}
}

💡 Giải thích:

  • Kiểm tra kết quả của hàm
  • So sánh với giá trị mong đợi
  • Báo lỗi nếu không khớp

2. Test với Subtest

func TestDivide(t *testing.T) {
tests := []struct {
name string
a, b float64
expected float64
wantErr bool
}{
{"chia bình thường", 10, 2, 5, false},
{"chia cho 0", 10, 0, 0, true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Divide(tt.a, tt.b)
if (err != nil) != tt.wantErr {
t.Errorf("Divide() lỗi = %v, mong đợi lỗi %v", err, tt.wantErr)
return
}
if !tt.wantErr && result != tt.expected {
t.Errorf("Divide() = %v, mong đợi %v", result, tt.expected)
}
})
}
}

💡 Giải thích:

  • Test nhiều trường hợp
  • Tổ chức test theo nhóm
  • Dễ dàng mở rộng

Table-Driven Tests 📊

1. Ví dụ Table-Driven Test

func TestValidateUser(t *testing.T) {
tests := []struct {
name string
user User
wantErr bool
}{
{
name: "user hợp lệ",
user: User{
Name: "John",
Email: "[email protected]",
},
wantErr: false,
},
{
name: "email không hợp lệ",
user: User{
Name: "John",
Email: "invalid-email",
},
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateUser(tt.user)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateUser() lỗi = %v, mong đợi lỗi %v", err, tt.wantErr)
}
})
}
}

💡 Giải thích:

  • Test nhiều trường hợp
  • Code ngắn gọn
  • Dễ bảo trì

Mocking 🎭

1. Interface Mocking

type Database interface {
GetUser(id string) (*User, error)
}

type MockDatabase struct {
users map[string]*User
}

func (m *MockDatabase) GetUser(id string) (*User, error) {
if user, ok := m.users[id]; ok {
return user, nil
}
return nil, errors.New("không tìm thấy user")
}

💡 Giải thích:

  • Tạo mock database
  • Giả lập dữ liệu
  • Dễ dàng test

2. Sử dụng Mock

func TestGetUser(t *testing.T) {
mockDB := &MockDatabase{
users: map[string]*User{
"1": {ID: "1", Name: "John"},
},
}

user, err := mockDB.GetUser("1")
if err != nil {
t.Fatalf("lỗi không mong đợi: %v", err)
}
if user.Name != "John" {
t.Errorf("nhận được %v, mong đợi %v", user.Name, "John")
}
}

💡 Giải thích:

  • Test với mock
  • Kiểm tra kết quả
  • Xử lý lỗi

Benchmarking 📈

1. Benchmark Cơ bản

func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}

💡 Giải thích:

  • Đo hiệu suất
  • Chạy nhiều lần
  • So sánh kết quả

2. Benchmark với Sub-benchmarks

func BenchmarkString(b *testing.B) {
tests := []struct {
name string
str string
}{
{"ngắn", "hello"},
{"dài", "hello world this is a long string"},
}

for _, tt := range tests {
b.Run(tt.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
ProcessString(tt.str)
}
})
}
}

💡 Giải thích:

  • Test nhiều trường hợp
  • So sánh hiệu suất
  • Tìm bottlenecks

Example Tests 📝

1. Example Function

func ExampleAdd() {
result := Add(2, 3)
fmt.Println(result)
// Output: 5
}

💡 Giải thích:

  • Ví dụ sử dụng
  • Kiểm tra output
  • Tài liệu hóa code

2. Example Method

func ExampleUser_String() {
user := User{Name: "John"}
fmt.Println(user)
// Output: User: John
}

💡 Giải thích:

  • Ví dụ method
  • Kiểm tra output
  • Tài liệu hóa

Test Coverage 📊

1. Chạy Test với Coverage

go test -cover ./...

💡 Giải thích:

  • Đo độ bao phủ
  • Kiểm tra toàn bộ code
  • Báo cáo chi tiết

2. Coverage Report

go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

💡 Giải thích:

  • Tạo báo cáo
  • Xem trực quan
  • Phân tích chi tiết

Integration Testing 🔄

1. HTTP Server Testing

func TestHTTPHandler(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(handleRequest))
defer server.Close()

resp, err := http.Get(server.URL)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
t.Errorf("nhận được %v, mong đợi %v", resp.StatusCode, http.StatusOK)
}
}

💡 Giải thích:

  • Test HTTP server
  • Kiểm tra response
  • Xử lý lỗi

2. Database Testing

func TestDatabase(t *testing.T) {
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatal(err)
}
defer db.Close()

// Thiết lập test database
if _, err := db.Exec(createTableSQL); err != nil {
t.Fatal(err)
}

// Chạy tests
// ...
}

💡 Giải thích:

  • Test database
  • Sử dụng in-memory
  • Tự động cleanup

Test Helpers 🛠️

1. Custom Test Helper

func assertEqual(t *testing.T, got, want interface{}) {
t.Helper()
if got != want {
t.Errorf("nhận được %v, mong đợi %v", got, want)
}
}

💡 Giải thích:

  • Tạo helper function
  • Code ngắn gọn
  • Dễ sử dụng

2. Sử dụng Helper

func TestSomething(t *testing.T) {
result := SomeFunction()
assertEqual(t, result, expected)
}

💡 Giải thích:

  • Sử dụng helper
  • Code sạch
  • Dễ đọc

Test Cleanup 🧹

1. Cleanup Sau Test

func TestWithCleanup(t *testing.T) {
// Thiết lập
tempDir := t.TempDir()

// Code test

// Tự động cleanup
}

💡 Giải thích:

  • Tự động cleanup
  • Không cần code thêm
  • An toàn

2. Cleanup Thủ công

func TestWithManualCleanup(t *testing.T) {
// Thiết lập
cleanup := setupTest(t)
defer cleanup()

// Code test
}

💡 Giải thích:

  • Cleanup thủ công
  • Linh hoạt
  • Kiểm soát tốt

Best Practices (Cách sử dụng tốt nhất) ✅

  1. Viết test cho mọi function

    // ✅ Đúng
    func TestAdd(t *testing.T) {
    result := Add(2, 3)
    assertEqual(t, result, 5)
    }

    // ❌ Sai
    // Không viết test
  2. Sử dụng table-driven tests

    // ✅ Đúng
    tests := []struct {
    name string
    input int
    want int
    }{
    {"số dương", 5, 5},
    {"số âm", -5, 5},
    }

    // ❌ Sai
    func TestAbs(t *testing.T) {
    if Abs(5) != 5 {
    t.Error("test số dương")
    }
    if Abs(-5) != 5 {
    t.Error("test số âm")
    }
    }
  3. Xử lý cleanup

    // ✅ Đúng
    func TestWithCleanup(t *testing.T) {
    tempDir := t.TempDir()
    defer os.RemoveAll(tempDir)
    }

    // ❌ Sai
    // Không cleanup
  4. Test edge cases

    // ✅ Đúng
    tests := []struct {
    name string
    input string
    wantErr bool
    }{
    {"chuỗi rỗng", "", true},
    {"chuỗi quá dài", strings.Repeat("a", 1000), true},
    }

    // ❌ Sai
    // Chỉ test trường hợp bình thường

Tiếp theo 🎯

Trong các bài học tiếp theo, chúng ta sẽ:

  • Tìm hiểu về performance
  • Học cách tối ưu code
  • Khám phá các kỹ thuật nâng cao
  • Thực hành với các dự án thực tế

💡 Lời khuyên: Viết test là một thói quen tốt. Hãy viết test ngay khi bạn viết code mới và chạy test thường xuyên để đảm bảo code của bạn luôn hoạt động đúng!