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) ✅
-
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 -
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")
}
} -
Xử lý cleanup
// ✅ Đúng
func TestWithCleanup(t *testing.T) {
tempDir := t.TempDir()
defer os.RemoveAll(tempDir)
}
// ❌ Sai
// Không cleanup -
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!