Gensics

Back to Home

Testing ใน Go: Unit Tests และ Integration Tests

NPXVERSE Team11 กรกฎาคม 2567Testing
GolangTestingUnit TestsBenchmarks

Unit Testing พื้นฐาน

Go มี testing framework ที่สร้างมาพร้อมกับตัวภาษา

การสร้าง Test Function

// math.go package main func Add(a, b int) int { return a + b } func Multiply(a, b int) int { return a * b } func Divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil }
// math_test.go package main import ( "testing" ) func TestAdd(t *testing.T) { result := Add(2, 3) expected := 5 if result != expected { t.Errorf("Add(2, 3) = %d; expected %d", result, expected) } } func TestMultiply(t *testing.T) { tests := []struct { name string a, b int expected int }{ {"positive numbers", 2, 3, 6}, {"zero", 0, 5, 0}, {"negative numbers", -2, 3, -6}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := Multiply(tt.a, tt.b) if result != tt.expected { t.Errorf("Multiply(%d, %d) = %d; expected %d", tt.a, tt.b, result, tt.expected) } }) } }

การรัน Tests

# รัน tests ทั้งหมด go test # รัน tests พร้อม verbose output go test -v # รัน specific test go test -run TestAdd # รัน tests ใน package ทั้งหมด go test ./...

Testing with Error Handling

func TestDivide(t *testing.T) { // Test successful division result, err := Divide(10, 2) if err != nil { t.Errorf("Unexpected error: %v", err) } if result != 5.0 { t.Errorf("Divide(10, 2) = %f; expected 5.0", result) } // Test division by zero _, err = Divide(10, 0) if err == nil { t.Error("Expected error for division by zero") } }

Advanced Testing Techniques

Table-Driven Tests

func TestCalculator(t *testing.T) { tests := []struct { name string operation string a, b float64 expected float64 expectErr bool }{ {"add positive", "add", 2.5, 3.5, 6.0, false}, {"subtract", "subtract", 10, 3, 7, false}, {"multiply", "multiply", 4, 2.5, 10, false}, {"divide", "divide", 15, 3, 5, false}, {"divide by zero", "divide", 10, 0, 0, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var result float64 var err error switch tt.operation { case "add": result = tt.a + tt.b case "subtract": result = tt.a - tt.b case "multiply": result = tt.a * tt.b case "divide": result, err = Divide(tt.a, tt.b) } if tt.expectErr { if err == nil { t.Error("Expected error but got none") } return } if err != nil { t.Errorf("Unexpected error: %v", err) } if result != tt.expected { t.Errorf("Expected %f, got %f", tt.expected, result) } }) } }

Mocking และ Interfaces

// user.go type UserService interface { GetUser(id int) (*User, error) CreateUser(user *User) error } type EmailService interface { SendEmail(to, subject, body string) error } type NotificationService struct { userService UserService emailService EmailService } func (n *NotificationService) SendWelcomeEmail(userID int) error { user, err := n.userService.GetUser(userID) if err != nil { return err } subject := "Welcome!" body := fmt.Sprintf("Welcome %s!", user.Name) return n.emailService.SendEmail(user.Email, subject, body) }
// user_test.go type mockUserService struct { users map[int]*User } func (m *mockUserService) GetUser(id int) (*User, error) { user, exists := m.users[id] if !exists { return nil, errors.New("user not found") } return user, nil } func (m *mockUserService) CreateUser(user *User) error { m.users[user.ID] = user return nil } type mockEmailService struct { sentEmails []Email } type Email struct { To string Subject string Body string } func (m *mockEmailService) SendEmail(to, subject, body string) error { m.sentEmails = append(m.sentEmails, Email{ To: to, Subject: subject, Body: body, }) return nil } func TestNotificationService_SendWelcomeEmail(t *testing.T) { // Setup mocks userService := &mockUserService{ users: map[int]*User{ 1: {ID: 1, Name: "John", Email: "john@example.com"}, }, } emailService := &mockEmailService{} notificationService := &NotificationService{ userService: userService, emailService: emailService, } // Test err := notificationService.SendWelcomeEmail(1) if err != nil { t.Errorf("Unexpected error: %v", err) } // Verify if len(emailService.sentEmails) != 1 { t.Errorf("Expected 1 email sent, got %d", len(emailService.sentEmails)) } email := emailService.sentEmails[0] if email.To != "john@example.com" { t.Errorf("Expected email to john@example.com, got %s", email.To) } if email.Subject != "Welcome!" { t.Errorf("Expected subject 'Welcome!', got %s", email.Subject) } }

Benchmarking

Performance Testing

func BenchmarkAdd(b *testing.B) { for i := 0; i < b.N; i++ { Add(2, 3) } } func BenchmarkMultiply(b *testing.B) { for i := 0; i < b.N; i++ { Multiply(5, 7) } } func BenchmarkSliceAppend(b *testing.B) { b.Run("without_prealloc", func(b *testing.B) { for i := 0; i < b.N; i++ { var slice []int for j := 0; j < 1000; j++ { slice = append(slice, j) } } }) b.Run("with_prealloc", func(b *testing.B) { for i := 0; i < b.N; i++ { slice := make([]int, 0, 1000) for j := 0; j < 1000; j++ { slice = append(slice, j) } } }) }

การรัน Benchmarks

# รัน benchmarks go test -bench=. # รัน benchmark specific go test -bench=BenchmarkAdd # รัน benchmark พร้อม memory allocation info go test -bench=. -benchmem # รัน benchmark หลายรอบ go test -bench=. -count=5

Integration Testing

HTTP API Testing

// main.go func setupRouter() *gin.Engine { r := gin.Default() r.GET("/users/:id", getUserByID) r.POST("/users", createUser) return r } func main() { r := setupRouter() r.Run(":8080") }
// main_test.go func TestGetUser(t *testing.T) { router := setupRouter() w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/users/1", nil) router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, w.Code) } var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) if err != nil { t.Errorf("Error parsing JSON response: %v", err) } if response["message"] != "User found" { t.Errorf("Expected message 'User found', got %s", response["message"]) } } func TestCreateUser(t *testing.T) { router := setupRouter() user := map[string]interface{}{ "name": "Test User", "email": "test@example.com", "age": 25, } jsonValue, _ := json.Marshal(user) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/users", bytes.NewBuffer(jsonValue)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) if w.Code != http.StatusCreated { t.Errorf("Expected status %d, got %d", http.StatusCreated, w.Code) } }

Test Coverage

# รัน tests พร้อม coverage go test -cover # สร้าง coverage profile go test -coverprofile=coverage.out # ดู coverage ใน HTML go tool cover -html=coverage.out # ดู coverage ใน terminal go tool cover -func=coverage.out

การเขียน tests ที่ดีใน Go ช่วยให้โค้ดมีคุณภาพสูง บั๊กน้อย และง่ายต่อการ maintain ในระยะยาว