Golang-14高阶编程_测试与基准
·
Golang高阶编程:测试与基准(单元测试、Table-Driven、Benchmark、Fuzzing)
1. Go测试基础
1.1 测试框架概述
Go内置了强大的测试框架,无需额外依赖:
package main
import (
"testing"
"fmt"
"strings"
)
// 被测试的函数
func Add(a, b int) int {
return a + b
}
func Multiply(a, b int) int {
return a * b
}
// 基础单元测试
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Add(2, 3) = %d; want 5", result)
}
}
// 更完整的测试
func TestMultiply(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 6},
{"with zero", 5, 0, 0},
{"negative numbers", -2, 3, -6},
{"both negative", -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; want %d",
tt.a, tt.b, result, tt.expected)
}
})
}
}
1.2 测试命令和工具
# 运行当前目录下的所有测试
go test
# 显示详细输出
go test -v
# 运行特定的测试
go test -run TestAdd
# 显示覆盖率
go test -cover
# 生成覆盖率报告
go test -coverprofile=coverage.out
go tool cover -html=coverage.out -o coverage.html
# 运行并显示测试时间
go test -v -timeout 30s
2. Table-Driven测试模式
2.1 基础Table-Driven测试
package main
import (
"testing"
"reflect"
"errors"
)
// 被测试的函数
func CalculateDiscount(price float64, discountPercent float64) (float64, error) {
if price < 0 {
return 0, errors.New("price cannot be negative")
}
if discountPercent < 0 || discountPercent > 100 {
return 0, errors.New("discount must be between 0 and 100")
}
discount := price * discountPercent / 100
return price - discount, nil
}
func Fibonacci(n int) int {
if n <= 1 {
return n
}
return Fibonacci(n-1) + Fibonacci(n-2)
}
// Table-Driven测试
func TestCalculateDiscount(t *testing.T) {
tests := []struct {
name string
price float64
discountPercent float64
expectedPrice float64
expectedError bool
errorContains string
}{
{
name: "normal discount",
price: 100.0,
discountPercent: 20.0,
expectedPrice: 80.0,
expectedError: false,
},
{
name: "no discount",
price: 100.0,
discountPercent: 0.0,
expectedPrice: 100.0,
expectedError: false,
},
{
name: "full discount",
price: 100.0,
discountPercent: 100.0,
expectedPrice: 0.0,
expectedError: false,
},
{
name: "negative price",
price: -10.0,
discountPercent: 20.0,
expectedPrice: 0.0,
expectedError: true,
errorContains: "price cannot be negative",
},
{
name: "invalid discount - negative",
price: 100.0,
discountPercent: -5.0,
expectedPrice: 0.0,
expectedError: true,
errorContains: "discount must be between",
},
{
name: "invalid discount - too high",
price: 100.0,
discountPercent: 150.0,
expectedPrice: 0.0,
expectedError: true,
errorContains: "discount must be between",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := CalculateDiscount(tt.price, tt.discountPercent)
if tt.expectedError {
if err == nil {
t.Errorf("expected error but got none")
return
}
if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) {
t.Errorf("expected error containing '%s', got '%s'",
tt.errorContains, err.Error())
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if result != tt.expectedPrice {
t.Errorf("CalculateDiscount(%f, %f) = %f; want %f",
tt.price, tt.discountPercent, result, tt.expectedPrice)
}
})
}
}
// 更复杂的Table-Driven测试
func TestFibonacci(t *testing.T) {
tests := []struct {
name string
input int
expected int
}}{
{"base case 0", 0, 0},
{"base case 1", 1, 1},
{"small number", 5, 5},
{"medium number", 10, 55},
{"larger number", 15, 610},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Fibonacci(tt.input)
if result != tt.expected {
t.Errorf("Fibonacci(%d) = %d; want %d",
tt.input, result, tt.expected)
}
})
}
}
2.2 高级Table-Driven测试技巧
package main
import (
"testing"
"fmt"
"runtime"
"time"
)
// 测试辅助函数
func TestWithSetupAndTeardown(t *testing.T) {
tests := []struct {
name string
setup func() (interface{}, func())
test func(t *testing.T, data interface{})
}{
{
name: "test with database",
setup: func() (interface{}, func()) {
// 模拟数据库连接
db := &mockDB{connected: true}
return db, func() {
db.Close()
}
},
test: func(t *testing.T, data interface{}) {
db := data.(*mockDB)
if !db.connected {
t.Error("database should be connected")
}
},
},
{
name: "test with file",
setup: func() (interface{}, func()) {
// 模拟文件操作
file := &mockFile{open: true}
return file, func() {
file.Close()
}
},
test: func(t *testing.T, data interface{}) {
file := data.(*mockFile)
if !file.open {
t.Error("file should be open")
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, cleanup := tt.setup()
defer cleanup()
tt.test(t, data)
})
}
}
// 并行Table-Driven测试
func TestParallelProcessing(t *testing.T) {
tests := []struct {
name string
input []int
expected int
}{
{"small array", []int{1, 2, 3, 4, 5}, 15},
{"medium array", []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 55},
{"large array", makeLargeArray(1000), 500500},
}
for _, tt := range tests {
tt := tt // 捕获循环变量
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // 并行执行
result := sumArray(tt.input)
if result != tt.expected {
t.Errorf("sumArray() = %d; want %d", result, tt.expected)
}
})
}
}
// 性能相关的Table-Driven测试
func TestPerformanceCharacteristics(t *testing.T) {
tests := []struct {
name string
size int
maxDuration time.Duration
maxMemoryMB float64
}{
{"small dataset", 1000, 10 * time.Millisecond, 1.0},
{"medium dataset", 10000, 100 * time.Millisecond, 10.0},
{"large dataset", 100000, 1 * time.Second, 100.0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 记录内存使用
var m1 runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&m1)
start := time.Now()
// 执行测试函数
data := processLargeDataset(tt.size)
_ = data
duration := time.Since(start)
// 检查内存使用
var m2 runtime.MemStats
runtime.ReadMemStats(&m2)
memoryUsed := float64(m2.Alloc-m1.Alloc) / 1024 / 1024
if duration > tt.maxDuration {
t.Errorf("processing took %v, expected max %v", duration, tt.maxDuration)
}
if memoryUsed > tt.maxMemoryMB {
t.Errorf("used %.2f MB memory, expected max %.2f MB", memoryUsed, tt.maxMemoryMB)
}
})
}
}
// 辅助类型和函数
type mockDB struct {
connected bool
}
func (db *mockDB) Close() {
db.connected = false
}
type mockFile struct {
open bool
}
func (file *mockFile) Close() {
file.open = false
}
func makeLargeArray(size int) []int {
arr := make([]int, size)
for i := 0; i < size; i++ {
arr[i] = i + 1
}
return arr
}
func sumArray(arr []int) int {
sum := 0
for _, v := range arr {
sum += v
}
return sum
}
func processLargeDataset(size int) []int {
result := make([]int, size)
for i := 0; i < size; i++ {
result[i] = i * i
}
return result
}
3. 基准测试 (Benchmark)
3.1 基础基准测试
package main
import (
"testing"
"fmt"
"strings"
"strconv"
)
// 被测试的函数
func ConcatenateStrings(strs []string) string {
result := ""
for _, s := range strs {
result += s
}
return result
}
func ConcatenateStringsBuilder(strs []string) string {
var builder strings.Builder
for _, s := range strs {
builder.WriteString(s)
}
return builder.String()
}
func ConcatenateStringsJoin(strs []string) string {
return strings.Join(strs, "")
}
// 基准测试
func BenchmarkConcatenateStrings(b *testing.B) {
strs := []string{"hello", "world", "this", "is", "a", "test"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ConcatenateStrings(strs)
}
}
func BenchmarkConcatenateStringsBuilder(b *testing.B) {
strs := []string{"hello", "world", "this", "is", "a", "test"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ConcatenateStringsBuilder(strs)
}
}
func BenchmarkConcatenateStringsJoin(b *testing.B) {
strs := []string{"hello", "world", "this", "is", "a", "test"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ConcatenateStringsJoin(strs)
}
}
// 不同数据规模的基准测试
func BenchmarkConcatenateSmall(b *testing.B) {
strs := []string{"a", "b", "c"}
benchmarkConcatenate(b, strs)
}
func BenchmarkConcatenateMedium(b *testing.B) {
strs := make([]string, 100)
for i := 0; i < 100; i++ {
strs[i] = strconv.Itoa(i)
}
benchmarkConcatenate(b, strs)
}
func BenchmarkConcatenateLarge(b *testing.B) {
strs := make([]string, 1000)
for i := 0; i < 1000; i++ {
strs[i] = fmt.Sprintf("string-%d", i)
}
benchmarkConcatenate(b, strs)
}
func benchmarkConcatenate(b *testing.B, strs []string) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ConcatenateStringsBuilder(strs)
}
}
3.2 高级基准测试技巧
package main
import (
"testing"
"sync"
"runtime"
"time"
"math/rand"
)
// 并发基准测试
func BenchmarkConcurrentMap(b *testing.B) {
b.Run("single-thread", func(b *testing.B) {
m := make(map[int]int)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m[i] = i
_ = m[i]
}
})
b.Run("multi-thread", func(b *testing.B) {
m := &sync.Map{}
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
m.Store(i, i)
m.Load(i)
i++
}
})
})
}
// 内存分配基准测试
func BenchmarkMemoryAllocation(b *testing.B) {
b.ReportAllocs() // 报告内存分配
b.Run("slice-preallocated", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
slice := make([]int, 1000)
for j := 0; j < 1000; j++ {
slice[j] = j
}
}
})
b.Run("slice-append", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
var slice []int
for j := 0; j < 1000; j++ {
slice = append(slice, j)
}
}
})
}
// 带有设置和清理的基准测试
func BenchmarkWithSetup(b *testing.B) {
// 基准测试前的设置
data := make([]int, 10000)
for i := 0; i < len(data); i++ {
data[i] = rand.Intn(1000)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
// 每次迭代都处理相同的数据
result := processData(data)
_ = result
}
}
// 比较不同算法的基准测试
func BenchmarkSortingAlgorithms(b *testing.B) {
sizes := []int{100, 1000, 10000}
for _, size := range sizes {
b.Run(fmt.Sprintf("size-%d", size), func(b *testing.B) {
data := make([]int, size)
for i := 0; i < size; i++ {
data[i] = rand.Intn(size)
}
b.Run("bubble-sort", func(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
testData := make([]int, len(data))
copy(testData, data)
b.StartTimer()
bubbleSort(testData)
}
})
b.Run("quick-sort", func(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
testData := make([]int, len(data))
copy(testData, data)
b.StartTimer()
quickSort(testData)
}
})
})
}
}
// 基准测试工具函数
func BenchmarkComparison(b *testing.B) {
benchmarks := []struct {
name string
fn func(b *testing.B)
}{
{"algorithm1", benchmarkAlgorithm1},
{"algorithm2", benchmarkAlgorithm2},
{"algorithm3", benchmarkAlgorithm3},
}
for _, bm := range benchmarks {
b.Run(bm.name, bm.fn)
}
}
// 辅助函数
func processData(data []int) int {
sum := 0
for _, v := range data {
sum += v
}
return sum
}
func bubbleSort(arr []int) {
n := len(arr)
for i := 0; i < n-1; i++ {
for j := 0; j < n-i-1; j++ {
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
}
func quickSort(arr []int) {
if len(arr) < 2 {
return
}
pivot := arr[0]
var less, greater []int
for _, v := range arr[1:] {
if v <= pivot {
less = append(less, v)
} else {
greater = append(greater, v)
}
}
quickSort(less)
quickSort(greater)
copy(arr, less)
arr[len(less)] = pivot
copy(arr[len(less)+1:], greater)
}
func benchmarkAlgorithm1(b *testing.B) {
for i := 0; i < b.N; i++ {
time.Sleep(time.Microsecond) // 模拟耗时操作
}
}
func benchmarkAlgorithm2(b *testing.B) {
for i := 0; i < b.N; i++ {
time.Sleep(time.Microsecond * 2) // 模拟更耗时的操作
}
}
func benchmarkAlgorithm3(b *testing.B) {
for i := 0; i < b.N; i++ {
time.Sleep(time.Microsecond / 2) // 模拟更快的操作
}
}
4. 模糊测试 (Fuzzing)
4.1 基础模糊测试
package main
import (
"testing"
"unicode"
"strings"
"strconv"
)
// 被测试的函数
func ReverseString(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
func IsPalindrome(s string) bool {
s = strings.ToLower(s)
s = strings.ReplaceAll(s, " ", "")
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
if s[i] != s[j] {
return false
}
}
return true
}
func ParseAndValidateInt(s string) (int, error) {
s = strings.TrimSpace(s)
if s == "" {
return 0, errors.New("empty string")
}
// 检查是否只包含数字和负号
for i, r := range s {
if i == 0 && r == '-' {
continue
}
if !unicode.IsDigit(r) {
return 0, errors.New("invalid character")
}
}
return strconv.Atoi(s)
}
// 模糊测试
func FuzzReverseString(f *testing.F) {
// 添加种子语料
f.Add("hello")
f.Add("world")
f.Add("a")
f.Add("")
f.Add("中文测试")
f.Fuzz(func(t *testing.T, input string) {
reversed := ReverseString(input)
doubleReversed := ReverseString(reversed)
if input != doubleReversed {
t.Errorf("ReverseString(ReverseString(%q)) = %q, want %q",
input, doubleReversed, input)
}
})
}
func FuzzIsPalindrome(f *testing.F) {
// 添加已知用例
testcases := []string{
"madam",
"racecar",
"hello",
"a man a plan a canal panama",
"",
"A man a plan a canal Panama",
}
for _, tc := range testcases {
f.Add(tc)
}
f.Fuzz(func(t *testing.T, input string) {
result := IsPalindrome(input)
// 验证基本属性
if input == "" {
if !result {
t.Errorf("empty string should be palindrome")
}
return
}
// 验证对称性
if result {
reversed := ReverseString(strings.ToLower(strings.ReplaceAll(input, " ", "")))
if input != reversed {
t.Errorf("palindrome check failed for %q", input)
}
}
})
}
func FuzzParseAndValidateInt(f *testing.F) {
// 添加有效输入
validInputs := []string{
"123",
"-456",
"0",
" 789 ",
"-0",
}
for _, input := range validInputs {
f.Add(input)
}
f.Fuzz(func(t *testing.T, input string) {
result, err := ParseAndValidateInt(input)
// 验证错误处理
if err != nil {
// 对于无效输入,应该返回错误
if input == "" {
return // 空字符串应该返回错误
}
// 检查是否包含无效字符
trimmed := strings.TrimSpace(input)
for _, r := range trimmed {
if !unicode.IsDigit(r) && r != '-' {
return // 包含无效字符,应该返回错误
}
}
t.Errorf("unexpected error for input %q: %v", input, err)
return
}
// 验证结果
if input == "" && err == nil {
t.Errorf("empty input should return error")
}
// 验证转换的正确性
expected, parseErr := strconv.Atoi(strings.TrimSpace(input))
if parseErr != nil && err == nil {
t.Errorf("ParseAndValidateInt succeeded where strconv.Atoi failed")
}
if parseErr == nil && result != expected {
t.Errorf("ParseAndValidateInt(%q) = %d, want %d", input, result, expected)
}
})
}
4.2 高级模糊测试
package main
import (
"testing"
"bytes"
"encoding/json"
"regexp"
"net/mail"
"time"
)
// 复杂的数据结构
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
Created time.Time `json:"created"`
IsActive bool `json:"is_active"`
}
type UserService struct {
users map[string]User
}
func NewUserService() *UserService {
return &UserService{
users: make(map[string]User),
}
}
func (s *UserService) CreateUser(user User) error {
if user.Name == "" {
return errors.New("name cannot be empty")
}
if !isValidEmail(user.Email) {
return errors.New("invalid email format")
}
if user.Age < 0 || user.Age > 150 {
return errors.New("age must be between 0 and 150")
}
if _, exists := s.users[user.Email]; exists {
return errors.New("user already exists")
}
user.Created = time.Now()
s.users[user.Email] = user
return nil
}
func (s *UserService) GetUser(email string) (User, error) {
user, exists := s.users[email]
if !exists {
return User{}, errors.New("user not found")
}
return user, nil
}
func isValidEmail(email string) bool {
_, err := mail.ParseAddress(email)
return err == nil
}
// 复杂的模糊测试
func FuzzUserService(f *testing.F) {
// 添加种子数据
seedUsers := []User{
{
Name: "John Doe",
Email: "john@example.com",
Age: 30,
IsActive: true,
},
{
Name: "Jane Smith",
Email: "jane@example.com",
Age: 25,
IsActive: false,
},
}
for _, user := range seedUsers {
data, _ := json.Marshal(user)
f.Add(data)
}
f.Fuzz(func(t *testing.T, userData []byte) {
var user User
if err := json.Unmarshal(userData, &user); err != nil {
return // 无效JSON,跳过
}
service := NewUserService()
// 测试创建用户
err := service.CreateUser(user)
// 验证结果
if err != nil {
// 验证错误情况
if user.Name == "" {
if !strings.Contains(err.Error(), "name cannot be empty") {
t.Errorf("expected name error, got: %v", err)
}
return
}
if !isValidEmail(user.Email) {
if !strings.Contains(err.Error(), "invalid email format") {
t.Errorf("expected email error, got: %v", err)
}
return
}
if user.Age < 0 || user.Age > 150 {
if !strings.Contains(err.Error(), "age must be between") {
t.Errorf("expected age error, got: %v", err)
}
return
}
return
}
// 验证成功创建的用户
retrievedUser, err := service.GetUser(user.Email)
if err != nil {
t.Errorf("failed to retrieve created user: %v", err)
return
}
if retrievedUser.Name != user.Name {
t.Errorf("user name mismatch: got %s, want %s", retrievedUser.Name, user.Name)
}
if retrievedUser.Age != user.Age {
t.Errorf("user age mismatch: got %d, want %d", retrievedUser.Age, user.Age)
}
})
}
// 正则表达式模糊测试
func FuzzRegexValidation(f *testing.F) {
// 测试各种正则表达式模式
patterns := []string{
`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`, // 邮箱
`^1[3-9]\d{9}$`, // 中国手机号
`^\d{4}-\d{2}-\d{2}$`, // 日期格式
`^[A-Za-z0-9]{6,20}$`, // 用户名
}
// 添加测试用例
testCases := []struct {
pattern string
input string
shouldMatch bool
}{
{patterns[0], "test@example.com", true},
{patterns[0], "invalid-email", false},
{patterns[1], "13812345678", true},
{patterns[1], "12345678901", false},
}
for _, tc := range testCases {
f.Add(tc.pattern, tc.input, tc.shouldMatch)
}
f.Fuzz(func(t *testing.T, pattern, input string, shouldMatch bool) {
re, err := regexp.Compile(pattern)
if err != nil {
return // 无效的正则表达式模式
}
matches := re.MatchString(input)
// 这里可以添加更复杂的验证逻辑
if shouldMatch && !matches {
t.Logf("Pattern %q should match input %q but doesn't", pattern, input)
}
})
}
// JSON处理的模糊测试
func FuzzJSONProcessing(f *testing.F) {
// 添加有效的JSON种子
validJSONs := []string{
`{"name": "test", "age": 25}`,
`{"items": [1, 2, 3], "active": true}`,
`{"nested": {"key": "value"}}`,
`[]`,
`{}`,
`null`,
}
for _, jsonStr := range validJSONs {
f.Add([]byte(jsonStr))
}
f.Fuzz(func(t *testing.T, jsonData []byte) {
// 验证JSON格式
if !json.Valid(jsonData) {
return // 无效JSON,跳过
}
var data interface{}
if err := json.Unmarshal(jsonData, &data); err != nil {
t.Errorf("failed to unmarshal valid JSON: %v", err)
return
}
// 重新序列化
reencoded, err := json.Marshal(data)
if err != nil {
t.Errorf("failed to marshal data: %v", err)
return
}
// 验证往返一致性
var data2 interface{}
if err := json.Unmarshal(reencoded, &data2); err != nil {
t.Errorf("failed to unmarshal reencoded JSON: %v", err)
return
}
// 使用reflect.DeepEqual比较数据
// 注意:在某些情况下,这可能会因为浮点数精度等问题失败
// 这里仅作为示例
if !reflect.DeepEqual(data, data2) {
t.Logf("data mismatch after round-trip")
}
})
}
// 缓冲区处理的模糊测试
func FuzzBufferOperations(f *testing.F) {
// 添加种子数据
f.Add([]byte("hello world"))
f.Add([]byte(""))
f.Add([]byte("binary\x00data"))
f.Add([]byte("unicode: 你好世界"))
f.Fuzz(func(t *testing.T, data []byte) {
var buf1, buf2 bytes.Buffer
// 测试各种缓冲区操作
buf1.Write(data)
buf2.Write(data)
// 验证内容一致性
if !bytes.Equal(buf1.Bytes(), buf2.Bytes()) {
t.Error("buffer contents should be equal")
}
// 测试读取操作
readData := make([]byte, len(data))
n, err := buf1.Read(readData)
if err != nil && err != io.EOF {
t.Errorf("unexpected error reading from buffer: %v", err)
}
if n > 0 && !bytes.Equal(readData[:n], data[:n]) {
t.Error("read data doesn't match written data")
}
// 测试重置
buf1.Reset()
if buf1.Len() != 0 {
t.Error("buffer should be empty after reset")
}
})
}
5. 测试最佳实践和工具
5.1 测试组织和管理
package main
import (
"testing"
"os"
"flag"
"fmt"
"runtime"
"net/http"
"net/http/httptest"
)
// 测试配置
var (
integrationTest = flag.Bool("integration", false, "run integration tests")
benchmarkTest = flag.Bool("benchmark", false, "run benchmark tests")
coverageTest = flag.Bool("coverage", false, "generate coverage report")
)
// 测试套件设置
func TestMain(m *testing.M) {
// 测试前的全局设置
fmt.Println("Setting up test environment...")
// 解析命令行参数
flag.Parse()
// 运行测试
code := m.Run()
// 测试后的清理
fmt.Println("Cleaning up test environment...")
os.Exit(code)
}
// 环境相关的测试
func TestWithEnvironment(t *testing.T) {
if os.Getenv("CI") == "true" {
t.Skip("skipping test in CI environment")
}
if runtime.GOOS == "windows" {
t.Skip("skipping test on Windows")
}
// 测试代码
result := doSomething()
if result != expected {
t.Errorf("unexpected result")
}
}
// HTTP测试
func TestHTTPHandler(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("Hello, World!"))
}
tests := []struct {
name string
method string
wantStatus int
wantBody string
}{
{"valid GET", http.MethodGet, http.StatusOK, "Hello, World!"},
{"invalid method", http.MethodPost, http.StatusMethodNotAllowed, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest(tt.method, "/test", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != tt.wantStatus {
t.Errorf("got status %d, want %d", w.Code, tt.wantStatus)
}
if tt.wantBody != "" && w.Body.String() != tt.wantBody {
t.Errorf("got body %q, want %q", w.Body.String(), tt.wantBody)
}
})
}
}
// 数据库测试(模拟)
func TestDatabaseOperations(t *testing.T) {
// 使用内存数据库进行测试
db := setupTestDB(t)
defer cleanupTestDB(db)
tests := []struct {
name string
test func(t *testing.T, db *testDB)
}{
{
name: "create user",
test: func(t *testing.T, db *testDB) {
user := User{Name: "Test User", Email: "test@example.com"}
err := db.CreateUser(user)
if err != nil {
t.Errorf("failed to create user: %v", err)
}
},
},
{
name: "get user",
test: func(t *testing.T, db *testDB) {
user := User{Name: "Test User", Email: "test@example.com"}
db.CreateUser(user)
retrieved, err := db.GetUser("test@example.com")
if err != nil {
t.Errorf("failed to get user: %v", err)
}
if retrieved.Name != user.Name {
t.Errorf("got name %q, want %q", retrieved.Name, user.Name)
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.test(t, db)
})
}
}
// 性能监控测试
func TestPerformanceMonitoring(t *testing.T) {
if testing.Short() {
t.Skip("skipping performance test in short mode")
}
// 记录开始时间
start := time.Now()
// 记录内存使用
var m1 runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&m1)
// 执行测试操作
result := performComplexOperation()
_ = result
// 检查性能指标
duration := time.Since(start)
if duration > time.Second {
t.Errorf("operation took too long: %v", duration)
}
// 检查内存使用
var m2 runtime.MemStats
runtime.ReadMemStats(&m2)
memoryUsed := m2.Alloc - m1.Alloc
const maxMemory = 10 * 1024 * 1024 // 10MB
if memoryUsed > maxMemory {
t.Errorf("used too much memory: %d bytes", memoryUsed)
}
}
// 辅助函数和类型
func doSomething() string {
return "result"
}
var expected = "result"
type testDB struct {
users map[string]User
}
func setupTestDB(t *testing.T) *testDB {
return &testDB{
users: make(map[string]User),
}
}
func cleanupTestDB(db *testDB) {
db.users = nil
}
func (db *testDB) CreateUser(user User) error {
if _, exists := db.users[user.Email]; exists {
return errors.New("user already exists")
}
db.users[user.Email] = user
return nil
}
func (db *testDB) GetUser(email string) (User, error) {
user, exists := db.users[email]
if !exists {
return User{}, errors.New("user not found")
}
return user, nil
}
func performComplexOperation() []int {
result := make([]int, 1000)
for i := 0; i < 1000; i++ {
result[i] = i * i
}
return result
}
6. 测试命令和工具使用
6.1 常用测试命令
# 运行所有测试
go test ./...
# 运行特定包的测试
go test ./pkg/...
# 运行特定测试函数
go test -run TestFunctionName
# 运行匹配模式的测试
go test -run "Test.*User"
# 显示详细输出
go test -v
# 显示测试覆盖率
go test -cover
# 生成覆盖率报告
go test -coverprofile=coverage.out
go tool cover -html=coverage.out -o coverage.html
# 运行基准测试
go test -bench=.
# 运行特定基准测试
go test -bench=BenchmarkFunctionName
# 运行基准测试并显示内存分配
go test -bench=. -benchmem
# 运行模糊测试
go test -fuzz=FuzzFunctionName
# 运行模糊测试指定时间
go test -fuzz=FuzzFunctionName -fuzztime=10s
# 运行集成测试(需要标记)
go test -tags=integration
# 跳过短测试
go test -short
# 设置超时时间
go test -timeout=30s
# 并行运行测试
go test -parallel=4
# 显示测试输出(即使测试通过)
go test -v -count=1
# 运行测试指定次数
go test -count=3
6.2 高级测试工具
package main
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
// 使用testify断言库
func TestWithTestify(t *testing.T) {
// 基础断言
result := Add(2, 3)
assert.Equal(t, 5, result, "Add(2, 3) should equal 5")
// 要求断言(失败时立即停止测试)
user, err := GetUser("test@example.com")
require.NoError(t, err, "should not return error")
require.NotNil(t, user, "user should not be nil")
// 更复杂的断言
assert.Contains(t, user.Email, "@", "email should contain @")
assert.Greater(t, user.Age, 0, "age should be positive")
assert.Len(t, user.Name, 0, "name should not be empty")
}
// 测试套件
type UserServiceTestSuite struct {
suite.Suite
service *UserService
}
func (suite *UserServiceTestSuite) SetupTest() {
suite.service = NewUserService()
}
func (suite *UserServiceTestSuite) TearDownTest() {
suite.service = nil
}
func (suite *UserServiceTestSuite) TestCreateUser() {
user := User{
Name: "Test User",
Email: "test@example.com",
Age: 25,
}
err := suite.service.CreateUser(user)
suite.NoError(err)
retrieved, err := suite.service.GetUser(user.Email)
suite.NoError(err)
suite.Equal(user.Name, retrieved.Name)
}
func (suite *UserServiceTestSuite) TestCreateDuplicateUser() {
user := User{
Name: "Test User",
Email: "test@example.com",
Age: 25,
}
err := suite.service.CreateUser(user)
suite.NoError(err)
err = suite.service.CreateUser(user)
suite.Error(err)
suite.Contains(err.Error(), "already exists")
}
func TestUserServiceSuite(t *testing.T) {
suite.Run(t, new(UserServiceTestSuite))
}
总结
Go的测试框架提供了全面而强大的测试能力:
- 单元测试:基础但功能完整,支持Table-Driven模式
- 基准测试:性能测试和比较,支持内存分析
- 模糊测试:自动生成测试用例,发现边界情况
- 测试工具:丰富的命令行工具和第三方库支持
最佳实践建议:
- 使用Table-Driven测试模式提高测试覆盖率
- 编写基准测试来验证性能改进
- 使用模糊测试发现潜在的边界情况
- 保持测试代码的简洁和可读性
- 定期运行测试并监控测试覆盖率
- 使用测试套件组织相关的测试用例
通过合理使用这些测试技术,可以构建高质量、可靠的Go应用程序。
更多推荐
所有评论(0)