Go语言中的测试:从单元测试到集成测试

Go语言中的测试:从单元测试到集成测试

作为一个写了十几年代码的Go后端老兵,我深刻体会到测试在应用开发中的重要性。好的测试可以保证代码质量,减少bug,提高代码的可维护性。Go语言内置了强大的测试框架,使得测试变得简单而高效。今天咱们就聊聊Go语言中的测试,从单元测试到集成测试,帮助你写出更可靠的代码。

单元测试

1. 基本用法

Go语言的测试框架使用testing包,测试文件以_test.go结尾。

go 复制代码
// calculator.go
package calculator

func Add(a, b int) int {
    return a + b
}

func Subtract(a, b int) int {
    return a - b
}
go 复制代码
// calculator_test.go
package calculator

import (
    "testing"
)

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a        int
        b        int
        expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -2, -3, -5},
        {"mixed numbers", 2, -3, -1},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

func TestSubtract(t *testing.T) {
    result := Subtract(5, 3)
    if result != 2 {
        t.Errorf("Subtract(5, 3) = %d, want 2", result)
    }
}

2. 表驱动测试

表驱动测试是Go语言中一种常见的测试模式,通过表格形式定义多个测试用例。

go 复制代码
func TestMultiply(t *testing.T) {
    tests := []struct {
        a        int
        b        int
        expected int
    }{
        {0, 0, 0},
        {1, 1, 1},
        {2, 3, 6},
        {-2, 3, -6},
        {2, -3, -6},
        {-2, -3, 6},
    }

    for i, tt := range tests {
        t.Run(fmt.Sprintf("Test case %d", i), func(t *testing.T) {
            result := Multiply(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Multiply(%d, %d) = %d, want %d", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

3. 测试覆盖率

Go语言提供了内置的测试覆盖率工具,帮助你了解测试覆盖情况。

bash 复制代码
# 运行测试并生成覆盖率报告
go test -cover

# 生成详细的覆盖率报告
go test -coverprofile=coverage.out

# 查看覆盖率报告
go tool cover -html=coverage.out

模拟(Mocking)

在测试中,我们经常需要模拟外部依赖,以隔离被测试的代码。

1. 手动模拟

go 复制代码
// user_service.go
package service

import (
    "errors"
)

// UserRepository 定义用户存储接口
type UserRepository interface {
    GetUserByID(id int) (string, error)
}

// UserService 定义用户服务
type UserService struct {
    repo UserRepository
}

// NewUserService 创建用户服务实例
func NewUserService(repo UserRepository) *UserService {
    return &UserService{repo: repo}
}

// GetUserName 根据ID获取用户名
func (s *UserService) GetUserName(id int) (string, error) {
    if id <= 0 {
        return "", errors.New("invalid user ID")
    }
    return s.repo.GetUserByID(id)
}
go 复制代码
// user_service_test.go
package service

import (
    "errors"
    "testing"
)

// mockUserRepository 模拟UserRepository接口
type mockUserRepository struct {
    users map[int]string
    err   error
}

// GetUserByID 实现UserRepository接口
func (m *mockUserRepository) GetUserByID(id int) (string, error) {
    if m.err != nil {
        return "", m.err
    }
    if user, ok := m.users[id]; ok {
        return user, nil
    }
    return "", errors.New("user not found")
}

func TestGetUserName(t *testing.T) {
    tests := []struct {
        name     string
        id       int
        users    map[int]string
        err      error
        expected string
        wantErr  bool
    }{
        {
            name:     "valid user",
            id:       1,
            users:    map[int]string{1: "Alice"},
            err:      nil,
            expected: "Alice",
            wantErr:  false,
        },
        {
            name:     "invalid ID",
            id:       -1,
            users:    nil,
            err:      nil,
            expected: "",
            wantErr:  true,
        },
        {
            name:     "user not found",
            id:       999,
            users:    map[int]string{1: "Alice"},
            err:      nil,
            expected: "",
            wantErr:  true,
        },
        {
            name:     "repository error",
            id:       1,
            users:    nil,
            err:      errors.New("database error"),
            expected: "",
            wantErr:  true,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            mockRepo := &mockUserRepository{
                users: tt.users,
                err:   tt.err,
            }
            service := NewUserService(mockRepo)

            result, err := service.GetUserName(tt.id)
            if (err != nil) != tt.wantErr {
                t.Errorf("GetUserName() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if result != tt.expected {
                t.Errorf("GetUserName() = %v, want %v", result, tt.expected)
            }
        })
    }
}

2. 使用模拟库

我们可以使用第三方库如mockery来自动生成模拟代码。

bash 复制代码
# 安装mockery
go install github.com/vektra/mockery/v2@latest

# 生成模拟代码
mockery --name=UserRepository

集成测试

集成测试用于测试多个组件之间的交互,确保系统作为一个整体正常工作。

1. 数据库集成测试

go 复制代码
import (
    "database/sql"
    "fmt"
    "testing"
    "time"
    _ "github.com/go-sql-driver/mysql"
)

func TestDatabaseIntegration(t *testing.T) {
    // 连接测试数据库
    db, err := sql.Open("mysql", "test:test@tcp(localhost:3306)/test_db")
    if err != nil {
        t.Fatalf("Failed to connect to database: %v", err)
    }
    defer db.Close()

    // 测试数据库连接
    if err := db.Ping(); err != nil {
        t.Fatalf("Failed to ping database: %v", err)
    }

    // 创建测试表
    _, err = db.Exec(`CREATE TABLE IF NOT EXISTS users (
        id INT PRIMARY KEY AUTO_INCREMENT,
        name VARCHAR(255) NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )`)
    if err != nil {
        t.Fatalf("Failed to create table: %v", err)
    }

    // 插入测试数据
相关推荐
嵌入式×边缘AI:打怪升级日志3 小时前
使用JsonRPC实现前后台
前端·后端
小码哥_常3 小时前
从0到1:Spring Boot 中WebSocket实战揭秘,开启实时通信新时代
后端
lolo大魔王4 小时前
Go语言的异常处理
开发语言·后端·golang
IT_陈寒6 小时前
Python多进程共享变量那个坑,我差点没爬出来
前端·人工智能·后端
码事漫谈6 小时前
2026软考高级·系统架构设计师备考指南
后端
AI茶水间管理员7 小时前
如何让LLM稳定输出 JSON 格式结果?
前端·人工智能·后端
其实是白羊7 小时前
我用 Vibe Coding 搓了一个 IDEA 插件,复制URI 再也不用手动拼了
后端·intellij idea
用户8356290780518 小时前
Python 操作 Word 文档节与页面设置
后端·python
酒後少女的夢8 小时前
设计模式教程
后端·架构