2)Golang 语言编程规范大全
2.1)Go 的特点与生态
2.1.1)Go 的特点
1. 并发模型:Goroutine 与 Channel(核心王牌)
- Goroutine(Go 程)
- 本质:是一种用户态的轻量级线程,由 Go 运行时调度和管理,而非操作系统内核。
- Channel(通道)
- 本质:是 goroutine 之间进行通信和同步的管道。它遵循 “不要通过共享内存来通信,而应通过通信来共享内存” 的设计哲学。
2. 极致的编译与部署体验
- 编译速度快:依赖清晰、语法简洁,使得 Go 的编译速度极快,像编译一个脚本一样。
- 静态链接:编译后生成一个独立的静态二进制文件。这个文件包含了程序运行所需的一切(包括它自己的运行时),不依赖外部的动态库(如 glibc)。
- 跨平台编译:通过简单的环境变量(如 GOOS, GOARCH)就能轻松编译出运行在任何主流操作系统(Windows, Linux, macOS)和架构(x86, ARM)上的可执行文件。
2.1.2)Go 的生态
Go 自带了一个功能丰富、高质量的标准库,覆盖了网络、加密、I/O、文本处理等常见任务。特别是 net/http 库,使得开发 HTTP 服务器和客户端变得异常简单。
典型应用场景:云原生、API 网关、微服务、 CLI 工具、分布式系统。
待补充...
2.2)Go 的基础语法
2.2.1)Go 的基础语法
程序结构:Hello World。
// 1. 声明该文件属于哪个包
// main 包是一个特殊的包,它表示一个可执行的程序
package main
// 2. 导入外部包,fmt 包用于格式化输入输出
import "fmt"
// 3. main 函数是程序的入口点
func main() {
// 4. 调用 fmt 包的 Println 函数在控制台打印一行文字
fmt.Println("Hello, World!")
}2.2.2)Go 的变量与常量
// 方式一:完整声明,显式指定类型
var name string = "Alice"
// 方式二:类型推断,编译器根据初始值推断类型
var age = 30 // 编译器推断为 int 类型
// 方式三:短变量声明(最常用!),只能在函数内部使用
isStudent := true
// 方式四:批量声明
var (
height float64 = 1.75
weight = 70.0 // 类型推断为 float64
)
// 方式五:先声明,后赋值(初始化为类型的零值)
var score int
score = 100
// 常量:使用 const 关键字声明,编译时确定其值。
const Pi = 3.14159
const (
StatusOK = 200
StatusNotFound = 404
)
// 优雅的枚举实现(iota 是常量计数器,从0开始)
const (
Monday = iota // 0
Tuesday // 1
Wednesday // 2
)2.2.3)Go 的数据类型
基本类型:整数、浮点数、复数、布尔值、字符串。
复合类型:数组、切片、映射、结构体、通道、接口。
2.2.4)Go 的控制结构
条件语句:if、switch。
循环语句:for、range。
跳转语句:break、continue、goto。
if-else 语句的特点:条件不需要括号,但主体必须有大括号。
x := 10
// 基本 if
if x > 5 {
fmt.Println("x is greater than 5")
}
// if 带简短语句(声明的变量作用域仅在 if-else 块内)
if y := calculate(); y > 0 {
fmt.Println("y is positive:", y)
} else {
fmt.Println("y is not positive:", y)
}
// 多分支
if score >= 90 {
fmt.Println("A")
} else if score >= 80 {
fmt.Println("B")
} else {
fmt.Println("C")
}Go 只有 for 一种循环语句,但形式多样。
// 1. 传统的 for 循环(类似 C/Java)
for i := 0; i < 5; i++ {
fmt.Println(i) // 输出 0,1,2,3,4
}
// 2. while 循环(Go 中没有 while 关键字)
n := 0
for n < 5 {
fmt.Println(n) // 输出 0,1,2,3,4
n++
}
// 3. 无限循环
for {
fmt.Println("Loop forever...")
// 通常配合 break 使用
break
}
// 4. for-range 遍历切片、映射等
nums := []int{1, 2, 3}
for index, value := range nums {
fmt.Printf("index: %d, value: %d\n", index, value)
}switch 语句: 比其它语言中的 switch 更强大、灵活。
fruit := "apple"
// 基本用法(自动 break,不需要显式写)
switch fruit {
case "apple":
fmt.Println("This is an apple")
case "banana":
fmt.Println("This is a banana")
default:
fmt.Println("Unknown fruit")
}
// 条件判断
score := 85
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B") // 输出 B
case score >= 70:
fmt.Println("C")
}
// 多值匹配
switch fruit {
case "apple", "pear":
fmt.Println("This is a pome fruit")
case "orange", "lemon":
fmt.Println("This is a citrus fruit")
}2.2.5)Go 的函数与闭包
// 基本函数,无返回值
func sayHello(name string) {
fmt.Println("Hello,", name)
}
// 单返回值
func add(a int, b int) int {
return a + b
}
// 多返回值(Go 的特色!)
func divide(a, b float64) (float64, error) {
if b == 0.0 {
return 0.0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}// 命名的返回值(result 在函数体内已声明)
func calculate(a, b int) (result int) {
result = a * b // 直接使用
return // 裸返回,自动返回 result
}// 匿名函数
double := func(x int) int {
return x * 2
}
fmt.Println(double(5)) // 10
// 闭包:函数可以捕获其所在作用域的变量
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
myAdder := adder()
fmt.Println(myAdder(1)) // 1
fmt.Println(myAdder(2)) // 3
fmt.Println(myAdder(3)) // 62.2.6)Go 的错误处理
Go 中的错误本质上是一个内置的接口类型:
type error interface {
Error() string
}创建错误
import (
"errors"
"fmt"
)
// 方式一:使用 errors.New 创建简单错误
err1 := errors.New("something went wrong")
// 方式二:使用 fmt.Errorf 创建格式化错误
name := "config.json"
err2 := fmt.Errorf("file %s not found", name)
// 方式三:自定义错误类型(实现 error 接口)
type MyError struct {
Code int
Message string
}
func (e *MyError) Error() string {
return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}
func process() error {
return &MyError{Code: 404, Message: "Resource not found"}
}检查错误(经典模式)
// 多返回值函数的典型调用方式
result, err := someFunction()
if err != nil {
// 错误处理:记录日志、返回错误、降级处理等
log.Printf("Operation failed: %v", err)
return err
}
// 成功时继续使用 result
fmt.Println("Result:", result)优雅的错误处理模式
错误包装(提供上下文信息)
import "github.com/pkg/errors" // 或者 Go 1.13+ 的 fmt.Errorf
func readConfig() error {
_, err := os.ReadFile("config.json")
if err != nil {
// 包装原始错误,添加上下文
return errors.Wrap(err, "readConfig failed")
// Go 1.13+ 也可以:return fmt.Errorf("readConfig failed: %w", err)
}
return nil
}
func main() {
err := readConfig()
if err != nil {
fmt.Printf("Error: %v\n", err) // 显示完整错误链
fmt.Printf("Original: %v\n", errors.Cause(err)) // 获取原始错误
}
}- 最佳实践:
- 对于可预见的错误(如文件不存在、网络超时),使用 error
- 只有在遇到不可恢复的程序错误时(如数组越界、空指针),才使用 panic
- defer 常用于资源清理(文件关闭、锁释放等)
2.2.7)Go 的接口与反射
接口是 Go 实现多态的核心方式,采用 隐式实现(Duck Typing)。
// 定义接口
type Writer interface {
Write([]byte) (int, error)
}
type Closer interface {
Close() error
}
// 组合接口
type WriteCloser interface {
Writer
Closer
}
// 实现接口(隐式实现,无需显式声明)
type File struct {
name string
}
func (f *File) Write(data []byte) (int, error) {
fmt.Printf("Writing to %s: %s\n", f.name, string(data))
return len(data), nil
}
func (f *File) Close() error {
fmt.Printf("Closing file %s\n", f.name)
return nil
}
// 使用接口
func process(wc WriteCloser) {
wc.Write([]byte("hello"))
wc.Close()
}
func main() {
file := &File{name: "test.txt"}
process(file) // File 自动实现了 WriteCloser 接口
}空接口可以保存任何类型的值,类似于 TypeScript 的 any 或 Java 的 Object。
// 空接口作为参数,可以接受任何类型
func printValue(v interface{}) {
fmt.Printf("Value: %v, Type: %T\n", v, v)
}
func main() {
printValue(42) // int
printValue("hello") // string
printValue([]int{1, 2, 3}) // []int
}反射允许程序在运行时检查类型信息和操作对象,通过 reflect 包实现。
import "reflect"
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func inspectValue(v interface{}) {
value := reflect.ValueOf(v)
typeOf := reflect.TypeOf(v)
fmt.Printf("Type: %s, Kind: %s\n", typeOf, value.Kind())
// 检查是否是结构体
if value.Kind() == reflect.Struct {
// 遍历结构体字段
for i := 0; i < value.NumField(); i++ {
field := typeOf.Field(i)
fieldValue := value.Field(i)
// 获取标签
jsonTag := field.Tag.Get("json")
fmt.Printf(" Field: %s, Tag: %s, Value: %v\n",
field.Name, jsonTag, fieldValue.Interface())
}
}
}
func main() {
p := Person{Name: "Alice", Age: 30}
inspectValue(p)
}- 反射的常见用途:
- JSON/XML 序列化与反序列化
- 数据库 ORM 映射
- 配置解析
- 通用函数库
2.2.8)Go 的包与模块
包的基本概念:
- 包是 Go 语言中组织代码的基本单位,用于将相关的代码组织在一起。
- 每个包都有一个唯一的包名,包名通常与包所在的目录名相同。
- 包中的函数、类型、变量等都可以被其他包引用。
- main 包是特殊包,用于生成可执行文件
包的导入:
- 使用
import关键字导入包。例如:import "fmt" - 可以使用
import关键字导入多个包,例如:import "fmt"; import "os" - 可以使用
import .导入包,这样可以直接使用包中的函数和变量,而不需要使用包名前缀。 - 可以使用
import _导入包,这样只会执行包的init函数,不会使用包中的其他内容。
包的初始化:
- 每个包都有一个
init函数,用于包的初始化。init函数在包被首次引用时自动执行。 init函数没有参数和返回值,不能被显式调用。- 一个包可以有多个
init函数,它们按照文件顺序执行。
包的可见性:在 Go 语言中,包的可见性是通过首字母大小写来控制的。首字母大写的标识符(函数、变量、类型等)是公开的,可以被其他包引用。首字母小写的标识符是私有的,只能在同一个包中使用。
包的依赖管理:
- Go 语言的依赖管理是通过
go.mod文件来实现的。go.mod文件定义了项目的模块名和依赖的版本。 - 使用
go mod init命令初始化go.mod文件。 - 使用
go get命令下载依赖包。 - 使用
go mod tidy命令清理依赖。
go mod tidy # 添加缺失的依赖,删除未使用的依赖
go mod download # 下载依赖到本地缓存
go mod vendor # 创建 vendor 目录(可选)
go list -m all # 查看所有依赖
go mod graph # 查看依赖图my-project/
├── go.mod # 模块定义
├── go.sum # 依赖校验
├── cmd/
│ └── myapp/
│ └── main.go # 程序入口
├── internal/ # 内部包(外部无法导入)
│ └── auth/
│ └── auth.go
├── pkg/ # 公共库包
│ └── utils/
│ └── stringutil.go
├── api/ # API 定义
├── configs/ # 配置文件
└── README.md # 项目文档2.3)Go 的数据结构
- 数组(Array)
- 数组:内存连续,访问速度快,但长度固定
- 切片(Slice)
- 切片:基于数组,动态扩容,是 Go 中最常用的数据结构
- 映射(Map)
- 映射:哈希表实现,平均 O(1) 时间复杂度,但内存开销较大
- 结构体(Struct)
- 结构体:内存紧凑,值语义,适合组织复杂数据
- 接口(Interface)
- 函数(Function)
- 通道(Channel)
- 并发(Concurrency)
2.4)Go 的并发编程
- 并发 vs 并行
- 并发:同时处理多个任务的能力(逻辑上同时)
- 并行:同时执行多个任务(物理上同时)
Go 的并发模型基于 CSP(Communicating Sequential Processes) 理论,核心思想是:"不要通过共享内存来通信,而应通过通信来共享内存"。
Goroutine 是 Go 的轻量级线程,由 Go 运行时管理。
package main
import (
"fmt"
"time"
)
// 普通函数
func sayHello(name string) {
for i := 0; i < 3; i++ {
fmt.Printf("Hello %s!\n", name)
time.Sleep(time.Millisecond * 100)
}
}
// 带延迟的函数
func slowFunction(id int) {
fmt.Printf("Goroutine %d started\n", id)
time.Sleep(time.Second * 2)
fmt.Printf("Goroutine %d finished\n", id)
}
func main() {
// 顺序执行
sayHello("Alice")
sayHello("Bob")
fmt.Println("--- 使用 Goroutine ---")
// 并发执行 - 使用 go 关键字
go sayHello("Charlie")
go sayHello("David")
// 启动多个 goroutine
for i := 1; i <= 3; i++ {
go slowFunction(i)
}
// 主 goroutine 等待,否则程序会退出
time.Sleep(time.Second * 3)
fmt.Println("Main function finished")
}# 打印结果
Hello Alice!
Hello Alice!
Hello Alice!
Hello Bob!
Hello Bob!
Hello Bob!
# 注意下面是乱序的
--- 使用 Goroutine ---
Hello Charlie!
Goroutine 3 started
Hello David!
Goroutine 2 started
Goroutine 1 started
Hello David!
Hello Charlie!
Hello Charlie!
Hello David!
Goroutine 1 finished
Goroutine 3 finished
Goroutine 2 finished
# 这个是固定的打印输出
Main function finishedChannel 是 goroutine 之间的通信管道,提供安全的同步机制。Channel 可以用于 goroutine 之间的数据传递和同步。
创建和使用 Channel
// 无缓冲 channel - 同步通信
ch1 := make(chan int)
// 有缓冲 channel - 异步通信
ch2 := make(chan string, 10) // 缓冲区大小为10
// 发送和接收数据
func main() {
ch := make(chan int)
// 生产者 goroutine
go func() {
fmt.Println("Sending data...")
ch <- 42 // 发送数据
fmt.Println("Data sent")
}()
// 消费者(主 goroutine)
fmt.Println("Waiting for data...")
data := <-ch // 接收数据(会阻塞直到有数据)
fmt.Printf("Received: %d\n", data)
}Channel 操作模式
// 1. 单向 Channel(限制方向)
func producer(ch chan<- int) { // 只写 channel
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // 关闭 channel
}
func consumer(ch <-chan int) { // 只读 channel
for num := range ch { // 自动检测 channel 关闭
fmt.Printf("Received: %d\n", num)
}
}
func main() {
ch := make(chan int, 3)
go producer(ch)
consumer(ch)
}Channel 的关闭和检测
func worker(tasks <-chan string, results chan<- string) {
for task := range tasks { // 自动检测 channel 关闭
fmt.Printf("Processing: %s\n", task)
results <- task + " done"
}
fmt.Println("Worker finished")
}
func main() {
tasks := make(chan string, 3)
results := make(chan string, 3)
// 启动 worker
go worker(tasks, results)
// 发送任务
tasks <- "task1"
tasks <- "task2"
tasks <- "task3"
close(tasks) // 重要:关闭 channel 通知接收方没有更多数据
// 接收结果
for i := 0; i < 3; i++ {
result := <-results
fmt.Printf("Result: %s\n", result)
}
}其他
- Select 语句
- 同步原语
- sync.WaitGroup
- WaitGroup
- Once
- Mutex(互斥锁)
- 并发模式
- Worker Pool 模式
- Fan-out, Fan-in 模式
- 上下文(Context)
最佳实践
- 并发编程最佳实践
- 优先使用 channel:遵循 "通过通信共享内存" 的原则
- 使用 context:为所有可能长时间运行的操作提供取消机制
- 避免 goroutine 泄漏:确保所有 goroutine 都能正常退出
- 谨慎使用共享内存:如果必须使用,一定要加锁
- 控制并发数量:使用 worker pool 模式避免资源耗尽
// 控制并发数量的示例
func controlledConcurrency(urls []string, maxConcurrency int) {
semaphore := make(chan struct{}, maxConcurrency) // 信号量
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
semaphore <- struct{}{} // 获取信号量
defer func() { <-semaphore }() // 释放信号量
// 执行任务
fmt.Printf("Fetching %s\n", u)
time.Sleep(time.Second * 2)
}(url)
}
wg.Wait()
}2.5)Go 的网络编程
Go 的 net 包提供了跨平台的网络 I/O 接口,支持 TCP、UDP、HTTP 等协议。
import (
"net"
"time"
)
// 基本网络操作
func networkBasics() {
// 解析地址
addr, err := net.ResolveTCPAddr("tcp", "google.com:80")
if err != nil {
panic(err)
}
fmt.Printf("Resolved address: %s\n", addr.String())
// 查找主机
ips, err := net.LookupIP("google.com")
if err != nil {
panic(err)
}
for _, ip := range ips {
fmt.Printf("IP: %s\n", ip.String())
}
}2.5.1)TCP/UDP 编程
// 简单的 TCP 服务器
func startTCPServer() {
// 监听 8080 端口
listener, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}
defer listener.Close()
fmt.Println("TCP Server listening on :8080")
for {
// 接受客户端连接
conn, err := listener.Accept()
if err != nil {
fmt.Printf("Accept error: %v\n", err)
continue
}
// 为每个客户端启动一个 goroutine
go handleTCPConnection(conn)
}
}
func handleTCPConnection(conn net.Conn) {
defer conn.Close()
// 设置超时
conn.SetDeadline(time.Now().Add(30 * time.Second))
clientAddr := conn.RemoteAddr().String()
fmt.Printf("Client connected: %s\n", clientAddr)
// 读取客户端数据
buffer := make([]byte, 1024)
for {
n, err := conn.Read(buffer)
if err != nil {
if err != io.EOF {
fmt.Printf("Read error from %s: %v\n", clientAddr, err)
}
break
}
message := string(buffer[:n])
fmt.Printf("Received from %s: %s", clientAddr, message)
// 回显数据
response := fmt.Sprintf("Echo: %s", message)
_, err = conn.Write([]byte(response))
if err != nil {
fmt.Printf("Write error to %s: %v\n", clientAddr, err)
break
}
// 如果是 "quit" 则断开连接
if strings.TrimSpace(message) == "quit" {
conn.Write([]byte("Goodbye!\n"))
break
}
}
fmt.Printf("Client disconnected: %s\n", clientAddr)
}func startTCPClient() {
// 连接服务器
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
panic(err)
}
defer conn.Close()
fmt.Println("Connected to server")
// 启动 goroutine 接收服务器响应
go func() {
buffer := make([]byte, 1024)
for {
n, err := conn.Read(buffer)
if err != nil {
if err != io.EOF {
fmt.Printf("Read error: %v\n", err)
}
return
}
fmt.Printf("Server response: %s", string(buffer[:n]))
}
}()
// 读取用户输入并发送
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("Enter message: ")
text, _ := reader.ReadString('\n')
_, err := conn.Write([]byte(text))
if err != nil {
fmt.Printf("Write error: %v\n", err)
break
}
if strings.TrimSpace(text) == "quit" {
break
}
}
}2.5.2)HTTP 编程
Go 的 net/http 包提供了完整的 HTTP 客户端和服务器实现。
// 基本 HTTP 处理函数
func helloHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "World"
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"message": fmt.Sprintf("Hello, %s!", name),
"time": time.Now().Format(time.RFC3339),
})
}
// 带参数的路由处理
func userHandler(w http.ResponseWriter, r *http.Request) {
vars := strings.Split(r.URL.Path, "/")
if len(vars) < 3 {
http.Error(w, "User ID required", http.StatusBadRequest)
return
}
userID := vars[2]
switch r.Method {
case "GET":
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"user_id": userID,
"name": "John Doe",
"email": "john@example.com",
})
case "POST":
// 处理创建用户逻辑
var user struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]interface{}{
"user_id": userID,
"created": true,
"user": user,
})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
// 中间件:日志记录
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 调用下一个处理器
next.ServeHTTP(w, r)
// 记录日志
fmt.Printf("%s %s %s %v\n",
r.Method, r.URL.Path, r.RemoteAddr, time.Since(start))
})
}
// 中间件:认证检查
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "Bearer secret-token" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
func startHTTPServer() {
// 创建多路复用器
mux := http.NewServeMux()
// 注册路由
mux.HandleFunc("/", helloHandler)
mux.HandleFunc("/user/", userHandler)
// 静态文件服务
mux.Handle("/static/",
http.StripPrefix("/static/",
http.FileServer(http.Dir("./static"))))
// 应用中间件
handler := loggingMiddleware(mux)
// 启动服务器
fmt.Println("HTTP Server listening on :8080")
err := http.ListenAndServe(":8080", handler)
if err != nil {
panic(err)
}
}func httpClientExamples() {
// 创建自定义 HTTP 客户端
client := &http.Client{
Timeout: 10 * time.Second,
}
// GET 请求
resp, err := client.Get("https://httpbin.org/get")
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Printf("GET Response: %s\n", string(body))
// POST 请求(JSON)
data := map[string]interface{}{
"name": "John",
"email": "john@example.com",
}
jsonData, _ := json.Marshal(data)
req, err := http.NewRequest("POST", "https://httpbin.org/post",
bytes.NewBuffer(jsonData))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer token123")
resp, err = client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ = io.ReadAll(resp.Body)
fmt.Printf("POST Response: %s\n", string(body))
}2.5.3)其他
- WebSocket 编程
- RPC 编程
- 高级网络模式:负载均衡、反向代理、服务发现等
2.6)Go 的测试与调试
2.6.1 单元测试
Go 的测试框架非常简单易用,测试文件以 _test.go 结尾。
基本测试结构如下:
// math.go
package math
func Add(a, b int) int {
return a + b
}
func Multiply(a, b int) int {
return a * b
}
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}// math_test.go
package math
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) {
testCases := []struct {
name string
a int
b int
expected int
}{
{"positive numbers", 2, 3, 6},
{"negative numbers", -2, -3, 6},
{"mixed signs", -2, 3, -6},
{"zero", 0, 5, 0},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := Multiply(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Multiply(%d, %d) = %d; expected %d",
tc.a, tc.b, result, tc.expected)
}
})
}
}
// 错误测试
func TestDivide(t *testing.T) {
t.Run("successful division", func(t *testing.T) {
result, err := Divide(10, 2)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if result != 5 {
t.Errorf("Expected 5, got %d", result)
}
})
t.Run("division by zero", func(t *testing.T) {
_, err := Divide(10, 0)
if err == nil {
t.Error("Expected error for division by zero, but got none")
}
expectedError := "division by zero"
if err.Error() != expectedError {
t.Errorf("Expected error '%s', got '%s'", expectedError, err.Error())
}
})
}运行测试
# 运行当前包的所有测试
go test
# 显示详细输出
go test -v
# 运行特定测试函数
go test -v -run TestAdd
# 运行匹配模式的测试
go test -v -run "TestDivide.*zero"
# 显示测试覆盖率
go test -cover
# 生成覆盖率报告
go test -coverprofile=coverage.out
go tool cover -html=coverage.out2.6.2 其他测试
- 基准测试
- 示例测试
- 测试辅助工具
- testify 断言库
- 调试技术
- 使用 fmt 调试
- 使用 log 调试
- 使用 delve 调试器
测试组织结构
project/
├── main.go
├── service/
│ ├── service.go
│ └── service_test.go
├── repository/
│ ├── repository.go
│ └── repository_test.go
└── internal/
└── utils/
├── utils.go
└── utils_test.go常用的测试命令总结
# 基本测试
go test ./... # 运行所有测试
go test -v ./... # 详细输出
# 基准测试
go test -bench=. -benchmem # 运行基准测试并显示内存分配
# 覆盖率
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
# 特定测试
go test -run TestName # 运行特定测试
go test -bench BenchmarkName # 运行特定基准测试
# 竞态检测
go test -race # 检测数据竞争
# 跳过缓存
go test -count=1 # 跳过测试缓存2.7)Go 的性能优化
Go 内置了强大的性能分析工具。
2.7.1 CPU 分析
import (
"os"
"runtime/pprof"
)
func cpuIntensiveTask() {
// 启动 CPU 分析
f, err := os.Create("cpu.prof")
if err != nil {
panic(err)
}
defer f.Close()
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// 执行需要分析的代码
for i := 0; i < 1000000; i++ {
_ = Multiply(i, i+1)
}
}2.7.2 内存分析
func memoryIntensiveTask() {
var data [][]int
for i := 0; i < 1000; i++ {
slice := make([]int, 1000)
data = append(data, slice)
}
// 写入内存分析数据
f, err := os.Create("memory.prof")
if err != nil {
panic(err)
}
defer f.Close()
pprof.WriteHeapProfile(f)
}2.7.3 HTTP 性能分析
import _ "net/http/pprof"
func startProfilingServer() {
// 在另一个端口启动性能分析端点
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// 然后可以通过以下方式访问:
// http://localhost:6060/debug/pprof/
}2.7.4 性能分析工具
- pprof:Go 内置的性能分析工具,可以生成 CPU、内存、阻塞等性能分析数据。
- go-torch:基于火焰图的性能分析工具,可以将 pprof 生成的数据转换为火焰图,更直观地展示性能瓶颈。
- gops:一个用于诊断 Go 程序的工具,可以查看程序的 CPU、内存、goroutine 等信息。
使用 pprof 工具
# 查看 CPU 分析结果
go tool pprof cpu.prof
# 查看内存分析结果
go tool pprof memory.prof
# 实时分析运行中的程序
go tool pprof http://localhost:6060/debug/pprof/profile
go tool pprof http://localhost:6060/debug/pprof/heap
# 生成火焰图
go tool pprof -http=:8080 cpu.prof2.8)Go 的项目实战
详见下一章节。