920 lines
25 KiB
Go
920 lines
25 KiB
Go
/*
|
||
04-testing.go - Go 语言测试详解
|
||
学习目标:
|
||
1. 理解 Go 语言测试的基本概念
|
||
2. 掌握单元测试的编写方法
|
||
3. 学会基准测试和性能分析
|
||
4. 了解测试覆盖率和测试工具
|
||
5. 掌握测试的最佳实践
|
||
知识点:
|
||
- 测试函数的命名和签名
|
||
- testing 包的使用
|
||
- 表驱动测试模式
|
||
- 基准测试和内存分析
|
||
- 测试覆盖率
|
||
- 测试工具和技巧
|
||
*/
|
||
|
||
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"sort"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// 这个文件演示了如何编写各种类型的测试
|
||
// 注意:实际的测试文件应该以 _test.go 结尾
|
||
|
||
func main() {
|
||
fmt.Println("=== Go 语言测试详解 ===\n")
|
||
|
||
// 演示测试的基本概念
|
||
demonstrateTestingBasics()
|
||
|
||
// 演示单元测试
|
||
demonstrateUnitTesting()
|
||
|
||
// 演示表驱动测试
|
||
demonstrateTableDrivenTests()
|
||
|
||
// 演示基准测试
|
||
demonstrateBenchmarkTesting()
|
||
|
||
// 演示示例测试
|
||
demonstrateExampleTesting()
|
||
|
||
// 演示测试工具和技巧
|
||
demonstrateTestingTools()
|
||
|
||
// 演示测试的最佳实践
|
||
demonstrateTestingBestPractices()
|
||
}
|
||
|
||
// demonstrateTestingBasics 演示测试的基本概念
|
||
func demonstrateTestingBasics() {
|
||
fmt.Println("1. 测试的基本概念:")
|
||
|
||
// 测试的重要性
|
||
fmt.Printf(" 测试的重要性:\n")
|
||
fmt.Printf(" - 验证代码的正确性\n")
|
||
fmt.Printf(" - 防止回归错误\n")
|
||
fmt.Printf(" - 提高代码质量\n")
|
||
fmt.Printf(" - 便于重构和维护\n")
|
||
fmt.Printf(" - 作为代码的文档\n")
|
||
|
||
// Go 测试的特点
|
||
fmt.Printf(" Go 测试的特点:\n")
|
||
fmt.Printf(" - 内置测试框架\n")
|
||
fmt.Printf(" - 简单的测试语法\n")
|
||
fmt.Printf(" - 强大的工具支持\n")
|
||
fmt.Printf(" - 并行测试支持\n")
|
||
fmt.Printf(" - 基准测试集成\n")
|
||
|
||
// 测试文件的组织
|
||
fmt.Printf(" 测试文件的组织:\n")
|
||
fmt.Printf(" 源文件: calculator.go\n")
|
||
fmt.Printf(" 测试文件: calculator_test.go\n")
|
||
fmt.Printf(" \n")
|
||
fmt.Printf(" 源文件: utils.go\n")
|
||
fmt.Printf(" 测试文件: utils_test.go\n")
|
||
|
||
// 测试函数的类型
|
||
fmt.Printf(" 测试函数的类型:\n")
|
||
fmt.Printf(" 1. 单元测试: func TestXxx(*testing.T)\n")
|
||
fmt.Printf(" 2. 基准测试: func BenchmarkXxx(*testing.B)\n")
|
||
fmt.Printf(" 3. 示例测试: func ExampleXxx()\n")
|
||
fmt.Printf(" 4. 模糊测试: func FuzzXxx(*testing.F) (Go 1.18+)\n")
|
||
|
||
// 运行测试的命令
|
||
fmt.Printf(" 运行测试的命令:\n")
|
||
fmt.Printf(" go test # 运行当前包的测试\n")
|
||
fmt.Printf(" go test ./... # 运行所有子包的测试\n")
|
||
fmt.Printf(" go test -v # 详细输出\n")
|
||
fmt.Printf(" go test -run TestAdd # 运行特定测试\n")
|
||
fmt.Printf(" go test -bench=. # 运行基准测试\n")
|
||
fmt.Printf(" go test -cover # 显示覆盖率\n")
|
||
|
||
fmt.Println()
|
||
}
|
||
|
||
// demonstrateUnitTesting 演示单元测试
|
||
func demonstrateUnitTesting() {
|
||
fmt.Println("2. 单元测试:")
|
||
|
||
// 基本测试函数
|
||
fmt.Printf(" 基本测试函数:\n")
|
||
fmt.Printf(" func TestAdd(t *testing.T) {\n")
|
||
fmt.Printf(" result := Add(2, 3)\n")
|
||
fmt.Printf(" expected := 5\n")
|
||
fmt.Printf(" if result != expected {\n")
|
||
fmt.Printf(" t.Errorf(\\\"Add(2, 3) = %%d; want %%d\\\", result, expected)\n")
|
||
fmt.Printf(" }\n")
|
||
fmt.Printf(" }\n")
|
||
|
||
// 模拟运行测试
|
||
fmt.Printf(" 模拟运行测试:\n")
|
||
mockT := &MockT{}
|
||
|
||
// 测试 Add 函数
|
||
fmt.Printf(" 测试 Add 函数:\n")
|
||
TestAdd(mockT)
|
||
if mockT.failed {
|
||
fmt.Printf(" ❌ 测试失败: %s\n", mockT.errorMsg)
|
||
} else {
|
||
fmt.Printf(" ✅ 测试通过\n")
|
||
}
|
||
|
||
// 测试 Divide 函数
|
||
fmt.Printf(" 测试 Divide 函数:\n")
|
||
mockT = &MockT{}
|
||
TestDivide(mockT)
|
||
if mockT.failed {
|
||
fmt.Printf(" ❌ 测试失败: %s\n", mockT.errorMsg)
|
||
} else {
|
||
fmt.Printf(" ✅ 测试通过\n")
|
||
}
|
||
|
||
// 测试 IsPrime 函数
|
||
fmt.Printf(" 测试 IsPrime 函数:\n")
|
||
mockT = &MockT{}
|
||
TestIsPrime(mockT)
|
||
if mockT.failed {
|
||
fmt.Printf(" ❌ 测试失败: %s\n", mockT.errorMsg)
|
||
} else {
|
||
fmt.Printf(" ✅ 测试通过\n")
|
||
}
|
||
|
||
// testing.T 的方法
|
||
fmt.Printf(" testing.T 的方法:\n")
|
||
fmt.Printf(" t.Error(args ...) # 记录错误但继续测试\n")
|
||
fmt.Printf(" t.Errorf(format, args ...) # 格式化错误信息\n")
|
||
fmt.Printf(" t.Fatal(args ...) # 记录错误并停止测试\n")
|
||
fmt.Printf(" t.Fatalf(format, args ...) # 格式化致命错误\n")
|
||
fmt.Printf(" t.Log(args ...) # 记录日志信息\n")
|
||
fmt.Printf(" t.Logf(format, args ...) # 格式化日志信息\n")
|
||
fmt.Printf(" t.Skip(args ...) # 跳过测试\n")
|
||
fmt.Printf(" t.Helper() # 标记为辅助函数\n")
|
||
|
||
fmt.Println()
|
||
}
|
||
|
||
// demonstrateTableDrivenTests 演示表驱动测试
|
||
func demonstrateTableDrivenTests() {
|
||
fmt.Println("3. 表驱动测试:")
|
||
|
||
// 表驱动测试的优势
|
||
fmt.Printf(" 表驱动测试的优势:\n")
|
||
fmt.Printf(" - 减少重复代码\n")
|
||
fmt.Printf(" - 易于添加新的测试用例\n")
|
||
fmt.Printf(" - 提高测试的可维护性\n")
|
||
fmt.Printf(" - 清晰的测试数据结构\n")
|
||
|
||
// 表驱动测试示例
|
||
fmt.Printf(" 表驱动测试示例:\n")
|
||
fmt.Printf(" func TestAddTable(t *testing.T) {\n")
|
||
fmt.Printf(" tests := []struct {\n")
|
||
fmt.Printf(" name string\n")
|
||
fmt.Printf(" a, b int\n")
|
||
fmt.Printf(" expected int\n")
|
||
fmt.Printf(" }{\n")
|
||
fmt.Printf(" {\\\"positive\\\", 2, 3, 5},\n")
|
||
fmt.Printf(" {\\\"negative\\\", -1, -2, -3},\n")
|
||
fmt.Printf(" {\\\"zero\\\", 0, 5, 5},\n")
|
||
fmt.Printf(" }\n")
|
||
fmt.Printf(" \n")
|
||
fmt.Printf(" for _, tt := range tests {\n")
|
||
fmt.Printf(" t.Run(tt.name, func(t *testing.T) {\n")
|
||
fmt.Printf(" result := Add(tt.a, tt.b)\n")
|
||
fmt.Printf(" if result != tt.expected {\n")
|
||
fmt.Printf(" t.Errorf(\\\"got %%d, want %%d\\\", result, tt.expected)\n")
|
||
fmt.Printf(" }\n")
|
||
fmt.Printf(" })\n")
|
||
fmt.Printf(" }\n")
|
||
fmt.Printf(" }\n")
|
||
|
||
// 模拟运行表驱动测试
|
||
fmt.Printf(" 模拟运行表驱动测试:\n")
|
||
mockT := &MockT{}
|
||
TestAddTable(mockT)
|
||
|
||
// 字符串处理的表驱动测试
|
||
fmt.Printf(" 字符串处理的表驱动测试:\n")
|
||
mockT = &MockT{}
|
||
TestStringOperations(mockT)
|
||
|
||
// 错误处理的表驱动测试
|
||
fmt.Printf(" 错误处理的表驱动测试:\n")
|
||
mockT = &MockT{}
|
||
TestValidateInput(mockT)
|
||
|
||
fmt.Println()
|
||
}
|
||
|
||
// demonstrateBenchmarkTesting 演示基准测试
|
||
func demonstrateBenchmarkTesting() {
|
||
fmt.Println("4. 基准测试:")
|
||
|
||
// 基准测试的概念
|
||
fmt.Printf(" 基准测试的概念:\n")
|
||
fmt.Printf(" - 测量代码的性能\n")
|
||
fmt.Printf(" - 比较不同实现的效率\n")
|
||
fmt.Printf(" - 识别性能瓶颈\n")
|
||
fmt.Printf(" - 验证优化效果\n")
|
||
|
||
// 基准测试函数
|
||
fmt.Printf(" 基准测试函数:\n")
|
||
fmt.Printf(" func BenchmarkStringConcat(b *testing.B) {\n")
|
||
fmt.Printf(" for i := 0; i < b.N; i++ {\n")
|
||
fmt.Printf(" _ = \\\"hello\\\" + \\\"world\\\"\n")
|
||
fmt.Printf(" }\n")
|
||
fmt.Printf(" }\n")
|
||
|
||
// 模拟运行基准测试
|
||
fmt.Printf(" 模拟运行基准测试:\n")
|
||
|
||
// 字符串连接基准测试
|
||
fmt.Printf(" 字符串连接性能比较:\n")
|
||
runBenchmark("StringConcat", BenchmarkStringConcat)
|
||
runBenchmark("StringBuffer", BenchmarkStringBuffer)
|
||
runBenchmark("StringBuilder", BenchmarkStringBuilder)
|
||
|
||
// 排序算法基准测试
|
||
fmt.Printf(" 排序算法性能比较:\n")
|
||
runBenchmark("BubbleSort", BenchmarkBubbleSort)
|
||
runBenchmark("QuickSort", BenchmarkQuickSort)
|
||
runBenchmark("StandardSort", BenchmarkStandardSort)
|
||
|
||
// 基准测试的运行参数
|
||
fmt.Printf(" 基准测试的运行参数:\n")
|
||
fmt.Printf(" go test -bench=. # 运行所有基准测试\n")
|
||
fmt.Printf(" go test -bench=BenchmarkAdd # 运行特定基准测试\n")
|
||
fmt.Printf(" go test -bench=. -benchmem # 显示内存分配\n")
|
||
fmt.Printf(" go test -bench=. -benchtime=10s # 设置运行时间\n")
|
||
fmt.Printf(" go test -bench=. -count=5 # 运行多次\n")
|
||
|
||
// 基准测试结果解读
|
||
fmt.Printf(" 基准测试结果解读:\n")
|
||
fmt.Printf(" BenchmarkAdd-8 1000000000 0.50 ns/op 0 B/op 0 allocs/op\n")
|
||
fmt.Printf(" │ │ │ │ │\n")
|
||
fmt.Printf(" │ │ │ │ └─ 每次操作的分配次数\n")
|
||
fmt.Printf(" │ │ │ └─ 每次操作分配的字节数\n")
|
||
fmt.Printf(" │ │ └─ 每次操作的平均时间\n")
|
||
fmt.Printf(" │ └─ 执行次数\n")
|
||
fmt.Printf(" └─ 基准测试名称和CPU核心数\n")
|
||
|
||
fmt.Println()
|
||
}
|
||
|
||
// demonstrateExampleTesting 演示示例测试
|
||
func demonstrateExampleTesting() {
|
||
fmt.Println("5. 示例测试:")
|
||
|
||
// 示例测试的概念
|
||
fmt.Printf(" 示例测试的概念:\n")
|
||
fmt.Printf(" - 提供函数使用示例\n")
|
||
fmt.Printf(" - 验证输出结果\n")
|
||
fmt.Printf(" - 生成文档\n")
|
||
fmt.Printf(" - 确保示例代码正确\n")
|
||
|
||
// 示例测试函数
|
||
fmt.Printf(" 示例测试函数:\n")
|
||
fmt.Printf(" func ExampleAdd() {\n")
|
||
fmt.Printf(" result := Add(2, 3)\n")
|
||
fmt.Printf(" fmt.Println(result)\n")
|
||
fmt.Printf(" // Output: 5\n")
|
||
fmt.Printf(" }\n")
|
||
|
||
// 运行示例测试
|
||
fmt.Printf(" 运行示例测试:\n")
|
||
fmt.Printf(" Add 函数示例:\n")
|
||
ExampleAdd()
|
||
|
||
fmt.Printf(" Divide 函数示例:\n")
|
||
ExampleDivide()
|
||
|
||
fmt.Printf(" StringReverse 函数示例:\n")
|
||
ExampleStringReverse()
|
||
|
||
// 示例测试的特点
|
||
fmt.Printf(" 示例测试的特点:\n")
|
||
fmt.Printf(" 1. 函数名以 Example 开头\n")
|
||
fmt.Printf(" 2. 使用 // Output: 注释指定期望输出\n")
|
||
fmt.Printf(" 3. 可以使用 // Unordered output: 处理无序输出\n")
|
||
fmt.Printf(" 4. 会在 go doc 中显示\n")
|
||
fmt.Printf(" 5. 可以通过 go test 验证\n")
|
||
|
||
fmt.Println()
|
||
}
|
||
|
||
// demonstrateTestingTools 演示测试工具和技巧
|
||
func demonstrateTestingTools() {
|
||
fmt.Println("6. 测试工具和技巧:")
|
||
|
||
// 测试覆盖率
|
||
fmt.Printf(" 测试覆盖率:\n")
|
||
fmt.Printf(" go test -cover # 显示覆盖率\n")
|
||
fmt.Printf(" go test -coverprofile=cover.out # 生成覆盖率文件\n")
|
||
fmt.Printf(" go tool cover -html=cover.out # 生成HTML报告\n")
|
||
fmt.Printf(" go tool cover -func=cover.out # 显示函数覆盖率\n")
|
||
|
||
// 测试辅助函数
|
||
fmt.Printf(" 测试辅助函数:\n")
|
||
fmt.Printf(" func assertEqual(t *testing.T, got, want interface{}) {\n")
|
||
fmt.Printf(" t.Helper() // 标记为辅助函数\n")
|
||
fmt.Printf(" if got != want {\n")
|
||
fmt.Printf(" t.Errorf(\\\"got %%v, want %%v\\\", got, want)\n")
|
||
fmt.Printf(" }\n")
|
||
fmt.Printf(" }\n")
|
||
|
||
// 模拟使用辅助函数
|
||
fmt.Printf(" 使用辅助函数:\n")
|
||
mockT := &MockT{}
|
||
TestWithHelpers(mockT)
|
||
if mockT.failed {
|
||
fmt.Printf(" ❌ 测试失败: %s\n", mockT.errorMsg)
|
||
} else {
|
||
fmt.Printf(" ✅ 测试通过\n")
|
||
}
|
||
|
||
// 子测试
|
||
fmt.Printf(" 子测试:\n")
|
||
fmt.Printf(" t.Run(\\\"subtest\\\", func(t *testing.T) {\n")
|
||
fmt.Printf(" // 子测试代码\n")
|
||
fmt.Printf(" })\n")
|
||
|
||
// 并行测试
|
||
fmt.Printf(" 并行测试:\n")
|
||
fmt.Printf(" func TestParallel(t *testing.T) {\n")
|
||
fmt.Printf(" t.Parallel() // 标记为并行测试\n")
|
||
fmt.Printf(" // 测试代码\n")
|
||
fmt.Printf(" }\n")
|
||
|
||
// 测试设置和清理
|
||
fmt.Printf(" 测试设置和清理:\n")
|
||
fmt.Printf(" func TestMain(m *testing.M) {\n")
|
||
fmt.Printf(" setup() // 全局设置\n")
|
||
fmt.Printf(" code := m.Run()\n")
|
||
fmt.Printf(" teardown() // 全局清理\n")
|
||
fmt.Printf(" os.Exit(code)\n")
|
||
fmt.Printf(" }\n")
|
||
|
||
// 跳过测试
|
||
fmt.Printf(" 跳过测试:\n")
|
||
fmt.Printf(" if testing.Short() {\n")
|
||
fmt.Printf(" t.Skip(\\\"跳过长时间运行的测试\\\")\n")
|
||
fmt.Printf(" }\n")
|
||
|
||
// 临时目录
|
||
fmt.Printf(" 临时目录:\n")
|
||
fmt.Printf(" tmpDir := t.TempDir() // 创建临时目录\n")
|
||
fmt.Printf(" // 测试结束后自动清理\n")
|
||
|
||
fmt.Println()
|
||
}
|
||
|
||
// demonstrateTestingBestPractices 演示测试的最佳实践
|
||
func demonstrateTestingBestPractices() {
|
||
fmt.Println("7. 测试的最佳实践:")
|
||
|
||
// 测试命名
|
||
fmt.Printf(" 测试命名:\n")
|
||
fmt.Printf(" 1. 使用描述性的测试名称\n")
|
||
fmt.Printf(" 2. 包含被测试的功能和场景\n")
|
||
fmt.Printf(" 3. 使用一致的命名约定\n")
|
||
fmt.Printf(" \n")
|
||
fmt.Printf(" 好的命名:\n")
|
||
fmt.Printf(" TestAdd_PositiveNumbers\n")
|
||
fmt.Printf(" TestDivide_ByZero_ReturnsError\n")
|
||
fmt.Printf(" TestUser_Create_ValidInput\n")
|
||
|
||
// 测试组织
|
||
fmt.Printf(" 测试组织:\n")
|
||
fmt.Printf(" 1. 每个源文件对应一个测试文件\n")
|
||
fmt.Printf(" 2. 按功能分组测试\n")
|
||
fmt.Printf(" 3. 使用子测试组织复杂场景\n")
|
||
fmt.Printf(" 4. 保持测试的独立性\n")
|
||
|
||
// 测试数据
|
||
fmt.Printf(" 测试数据:\n")
|
||
fmt.Printf(" 1. 使用表驱动测试处理多个用例\n")
|
||
fmt.Printf(" 2. 包含边界条件和异常情况\n")
|
||
fmt.Printf(" 3. 使用有意义的测试数据\n")
|
||
fmt.Printf(" 4. 避免硬编码的魔法数字\n")
|
||
|
||
// 断言
|
||
fmt.Printf(" 断言:\n")
|
||
fmt.Printf(" 1. 使用清晰的错误消息\n")
|
||
fmt.Printf(" 2. 一个测试只验证一个行为\n")
|
||
fmt.Printf(" 3. 优先使用 Errorf 而不是 Error\n")
|
||
fmt.Printf(" 4. 在必要时使用 Fatal 停止测试\n")
|
||
|
||
// 测试覆盖率
|
||
fmt.Printf(" 测试覆盖率:\n")
|
||
fmt.Printf(" 1. 追求高覆盖率但不盲目追求100%%\n")
|
||
fmt.Printf(" 2. 关注重要的业务逻辑\n")
|
||
fmt.Printf(" 3. 测试边界条件和错误路径\n")
|
||
fmt.Printf(" 4. 使用覆盖率工具识别遗漏\n")
|
||
|
||
// 性能考虑
|
||
fmt.Printf(" 性能考虑:\n")
|
||
fmt.Printf(" 1. 避免在测试中进行复杂计算\n")
|
||
fmt.Printf(" 2. 使用并行测试提高速度\n")
|
||
fmt.Printf(" 3. 合理使用测试缓存\n")
|
||
fmt.Printf(" 4. 避免不必要的外部依赖\n")
|
||
|
||
// 测试维护
|
||
fmt.Printf(" 测试维护:\n")
|
||
fmt.Printf(" 1. 保持测试代码的简洁\n")
|
||
fmt.Printf(" 2. 定期重构测试代码\n")
|
||
fmt.Printf(" 3. 及时更新过时的测试\n")
|
||
fmt.Printf(" 4. 删除无用的测试\n")
|
||
|
||
// 常见陷阱
|
||
fmt.Printf(" 常见陷阱:\n")
|
||
fmt.Printf(" 1. 测试依赖外部状态\n")
|
||
fmt.Printf(" 2. 测试之间相互依赖\n")
|
||
fmt.Printf(" 3. 忽略错误处理的测试\n")
|
||
fmt.Printf(" 4. 过度复杂的测试逻辑\n")
|
||
fmt.Printf(" 5. 不稳定的测试(flaky tests)\n")
|
||
|
||
fmt.Println()
|
||
}
|
||
|
||
// ========== 被测试的函数 ==========
|
||
|
||
// Add 计算两个整数的和
|
||
func Add(a, b int) int {
|
||
return a + b
|
||
}
|
||
|
||
// Divide 计算两个数的商
|
||
func Divide(a, b float64) (float64, error) {
|
||
if b == 0 {
|
||
return 0, fmt.Errorf("division by zero")
|
||
}
|
||
return a / b, nil
|
||
}
|
||
|
||
// IsPrime 判断是否为质数
|
||
func IsPrime(n int) bool {
|
||
if n < 2 {
|
||
return false
|
||
}
|
||
for i := 2; i*i <= n; i++ {
|
||
if n%i == 0 {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
// StringReverse 反转字符串
|
||
func StringReverse(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)
|
||
}
|
||
|
||
// Capitalize 将字符串首字母大写
|
||
func Capitalize(s string) string {
|
||
if len(s) == 0 {
|
||
return s
|
||
}
|
||
return strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
||
}
|
||
|
||
// ValidateInput 验证输入
|
||
func ValidateInput(input string) error {
|
||
if len(input) == 0 {
|
||
return fmt.Errorf("input cannot be empty")
|
||
}
|
||
if len(input) > 100 {
|
||
return fmt.Errorf("input too long")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ========== 模拟的 testing.T 和 testing.B ==========
|
||
|
||
// MockT 模拟 testing.T 类型
|
||
type MockT struct {
|
||
failed bool
|
||
errorMsg string
|
||
logs []string
|
||
}
|
||
|
||
func (m *MockT) Error(args ...interface{}) {
|
||
m.failed = true
|
||
m.errorMsg = fmt.Sprint(args...)
|
||
}
|
||
|
||
func (m *MockT) Errorf(format string, args ...interface{}) {
|
||
m.failed = true
|
||
m.errorMsg = fmt.Sprintf(format, args...)
|
||
}
|
||
|
||
func (m *MockT) Fatal(args ...interface{}) {
|
||
m.failed = true
|
||
m.errorMsg = fmt.Sprint(args...)
|
||
}
|
||
|
||
func (m *MockT) Fatalf(format string, args ...interface{}) {
|
||
m.failed = true
|
||
m.errorMsg = fmt.Sprintf(format, args...)
|
||
}
|
||
|
||
func (m *MockT) Log(args ...interface{}) {
|
||
m.logs = append(m.logs, fmt.Sprint(args...))
|
||
}
|
||
|
||
func (m *MockT) Logf(format string, args ...interface{}) {
|
||
m.logs = append(m.logs, fmt.Sprintf(format, args...))
|
||
}
|
||
|
||
func (m *MockT) Helper() {
|
||
// 标记为辅助函数
|
||
}
|
||
|
||
func (m *MockT) Run(name string, f func(*MockT)) bool {
|
||
fmt.Printf(" 运行子测试: %s\n", name)
|
||
subT := &MockT{}
|
||
f(subT)
|
||
if subT.failed {
|
||
fmt.Printf(" ❌ 失败: %s\n", subT.errorMsg)
|
||
return false
|
||
} else {
|
||
fmt.Printf(" ✅ 通过\n")
|
||
return true
|
||
}
|
||
}
|
||
|
||
// MockB 模拟 testing.B 类型
|
||
type MockB struct {
|
||
N int
|
||
}
|
||
|
||
func (b *MockB) ResetTimer() {}
|
||
func (b *MockB) ReportAllocs() {}
|
||
|
||
// ========== 测试函数示例 ==========
|
||
|
||
// TestAdd 测试 Add 函数
|
||
func TestAdd(t *MockT) {
|
||
result := Add(2, 3)
|
||
expected := 5
|
||
if result != expected {
|
||
t.Errorf("Add(2, 3) = %d; want %d", result, expected)
|
||
}
|
||
}
|
||
|
||
// TestDivide 测试 Divide 函数
|
||
func TestDivide(t *MockT) {
|
||
// 正常情况
|
||
result, err := Divide(10, 2)
|
||
if err != nil {
|
||
t.Errorf("Divide(10, 2) returned error: %v", err)
|
||
return
|
||
}
|
||
if result != 5 {
|
||
t.Errorf("Divide(10, 2) = %f; want 5", result)
|
||
}
|
||
|
||
// 除零情况
|
||
_, err = Divide(10, 0)
|
||
if err == nil {
|
||
t.Error("Divide(10, 0) should return error")
|
||
}
|
||
}
|
||
|
||
// TestIsPrime 测试 IsPrime 函数
|
||
func TestIsPrime(t *MockT) {
|
||
tests := []struct {
|
||
n int
|
||
expected bool
|
||
}{
|
||
{2, true},
|
||
{3, true},
|
||
{4, false},
|
||
{17, true},
|
||
{25, false},
|
||
{1, false},
|
||
{0, false},
|
||
{-1, false},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
result := IsPrime(tt.n)
|
||
if result != tt.expected {
|
||
t.Errorf("IsPrime(%d) = %t; want %t", tt.n, result, tt.expected)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestAddTable 表驱动测试示例
|
||
func TestAddTable(t *MockT) {
|
||
tests := []struct {
|
||
name string
|
||
a, b int
|
||
expected int
|
||
}{
|
||
{"positive numbers", 2, 3, 5},
|
||
{"negative numbers", -1, -2, -3},
|
||
{"mixed numbers", -1, 2, 1},
|
||
{"zero", 0, 5, 5},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(subT *MockT) {
|
||
result := Add(tt.a, tt.b)
|
||
if result != tt.expected {
|
||
subT.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestStringOperations 字符串操作测试
|
||
func TestStringOperations(t *MockT) {
|
||
tests := []struct {
|
||
name string
|
||
input string
|
||
expected string
|
||
}{
|
||
{"normal string", "hello", "Hello"},
|
||
{"empty string", "", ""},
|
||
{"single char", "a", "A"},
|
||
{"already capitalized", "Hello", "Hello"},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(subT *MockT) {
|
||
result := Capitalize(tt.input)
|
||
if result != tt.expected {
|
||
subT.Errorf("Capitalize(%q) = %q; want %q", tt.input, result, tt.expected)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestValidateInput 输入验证测试
|
||
func TestValidateInput(t *MockT) {
|
||
tests := []struct {
|
||
name string
|
||
input string
|
||
wantError bool
|
||
}{
|
||
{"valid input", "hello", false},
|
||
{"empty input", "", true},
|
||
{"too long input", strings.Repeat("a", 101), true},
|
||
{"max length input", strings.Repeat("a", 100), false},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(subT *MockT) {
|
||
err := ValidateInput(tt.input)
|
||
if (err != nil) != tt.wantError {
|
||
subT.Errorf("ValidateInput(%q) error = %v; wantError %t", tt.input, err, tt.wantError)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestWithHelpers 使用辅助函数的测试
|
||
func TestWithHelpers(t *MockT) {
|
||
assertEqual := func(t *MockT, got, want interface{}) {
|
||
t.Helper()
|
||
if got != want {
|
||
t.Errorf("got %v, want %v", got, want)
|
||
}
|
||
}
|
||
|
||
// 使用辅助函数进行断言
|
||
assertEqual(t, Add(2, 3), 5)
|
||
assertEqual(t, Capitalize("hello"), "Hello")
|
||
|
||
result, err := Divide(10, 2)
|
||
if err != nil {
|
||
t.Errorf("unexpected error: %v", err)
|
||
return
|
||
}
|
||
assertEqual(t, result, 5.0)
|
||
}
|
||
|
||
// ========== 基准测试函数 ==========
|
||
|
||
// BenchmarkStringConcat 字符串连接基准测试
|
||
func BenchmarkStringConcat(b *MockB) {
|
||
for i := 0; i < b.N; i++ {
|
||
_ = "hello" + "world" + "golang" + "benchmark"
|
||
}
|
||
}
|
||
|
||
// BenchmarkStringBuffer 字符串缓冲区基准测试
|
||
func BenchmarkStringBuffer(b *MockB) {
|
||
for i := 0; i < b.N; i++ {
|
||
var buffer strings.Builder
|
||
buffer.WriteString("hello")
|
||
buffer.WriteString("world")
|
||
buffer.WriteString("golang")
|
||
buffer.WriteString("benchmark")
|
||
_ = buffer.String()
|
||
}
|
||
}
|
||
|
||
// BenchmarkStringBuilder 字符串构建器基准测试
|
||
func BenchmarkStringBuilder(b *MockB) {
|
||
for i := 0; i < b.N; i++ {
|
||
parts := []string{"hello", "world", "golang", "benchmark"}
|
||
_ = strings.Join(parts, "")
|
||
}
|
||
}
|
||
|
||
// BenchmarkBubbleSort 冒泡排序基准测试
|
||
func BenchmarkBubbleSort(b *MockB) {
|
||
data := []int{64, 34, 25, 12, 22, 11, 90}
|
||
for i := 0; i < b.N; i++ {
|
||
bubbleSort(append([]int(nil), data...))
|
||
}
|
||
}
|
||
|
||
// BenchmarkQuickSort 快速排序基准测试
|
||
func BenchmarkQuickSort(b *MockB) {
|
||
data := []int{64, 34, 25, 12, 22, 11, 90}
|
||
for i := 0; i < b.N; i++ {
|
||
quickSort(append([]int(nil), data...))
|
||
}
|
||
}
|
||
|
||
// BenchmarkStandardSort 标准排序基准测试
|
||
func BenchmarkStandardSort(b *MockB) {
|
||
data := []int{64, 34, 25, 12, 22, 11, 90}
|
||
for i := 0; i < b.N; i++ {
|
||
sort.Ints(append([]int(nil), data...))
|
||
}
|
||
}
|
||
|
||
// ========== 示例测试函数 ==========
|
||
|
||
// ExampleAdd Add 函数的示例
|
||
func ExampleAdd() {
|
||
result := Add(2, 3)
|
||
fmt.Println(result)
|
||
// Output: 5
|
||
}
|
||
|
||
// ExampleDivide Divide 函数的示例
|
||
func ExampleDivide() {
|
||
result, err := Divide(10, 2)
|
||
if err != nil {
|
||
fmt.Printf("Error: %v", err)
|
||
return
|
||
}
|
||
fmt.Printf("%.1f", result)
|
||
// Output: 5.0
|
||
}
|
||
|
||
// ExampleStringReverse StringReverse 函数的示例
|
||
func ExampleStringReverse() {
|
||
result := StringReverse("hello")
|
||
fmt.Println(result)
|
||
// Output: olleh
|
||
}
|
||
|
||
// ========== 辅助函数 ==========
|
||
|
||
// runBenchmark 运行基准测试
|
||
func runBenchmark(name string, benchFunc func(*MockB)) {
|
||
b := &MockB{N: 1000000}
|
||
start := time.Now()
|
||
benchFunc(b)
|
||
duration := time.Since(start)
|
||
nsPerOp := duration.Nanoseconds() / int64(b.N)
|
||
fmt.Printf(" %s: %d ns/op\n", name, nsPerOp)
|
||
}
|
||
|
||
// bubbleSort 冒泡排序
|
||
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]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// quickSort 快速排序
|
||
func quickSort(arr []int) {
|
||
if len(arr) < 2 {
|
||
return
|
||
}
|
||
|
||
pivot := partition(arr)
|
||
quickSort(arr[:pivot])
|
||
quickSort(arr[pivot+1:])
|
||
}
|
||
|
||
// partition 分区函数
|
||
func partition(arr []int) int {
|
||
pivot := arr[len(arr)-1]
|
||
i := -1
|
||
|
||
for j := 0; j < len(arr)-1; j++ {
|
||
if arr[j] <= pivot {
|
||
i++
|
||
arr[i], arr[j] = arr[j], arr[i]
|
||
}
|
||
}
|
||
|
||
arr[i+1], arr[len(arr)-1] = arr[len(arr)-1], arr[i+1]
|
||
return i + 1
|
||
}
|
||
|
||
/*
|
||
运行这个程序:
|
||
go run 04-testing.go
|
||
|
||
实际测试文件示例(calculator_test.go):
|
||
package calculator
|
||
|
||
import "testing"
|
||
|
||
func TestAdd(t *testing.T) {
|
||
result := Add(2, 3)
|
||
expected := 5
|
||
if result != expected {
|
||
t.Errorf("Add(2, 3) = %d; want %d", result, expected)
|
||
}
|
||
}
|
||
|
||
func TestAddTable(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
a, b int
|
||
expected int
|
||
}{
|
||
{"positive numbers", 2, 3, 5},
|
||
{"negative numbers", -1, -2, -3},
|
||
{"mixed numbers", -1, 2, 1},
|
||
{"zero", 0, 5, 5},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
result := Add(tt.a, tt.b)
|
||
if result != tt.expected {
|
||
t.Errorf("got %d, want %d", result, tt.expected)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func BenchmarkStringConcat(b *testing.B) {
|
||
for i := 0; i < b.N; i++ {
|
||
_ = "hello" + "world"
|
||
}
|
||
}
|
||
|
||
func ExampleAdd() {
|
||
result := Add(2, 3)
|
||
fmt.Println(result)
|
||
// Output: 5
|
||
}
|
||
|
||
运行测试命令:
|
||
go test # 运行当前包的所有测试
|
||
go test -v # 详细输出
|
||
go test -run TestAdd # 运行特定测试
|
||
go test -bench=. # 运行基准测试
|
||
go test -cover # 显示覆盖率
|
||
go test ./... # 运行所有子包的测试
|
||
|
||
测试的核心概念:
|
||
1. 单元测试:验证单个函数或方法的正确性
|
||
2. 表驱动测试:使用测试数据表驱动测试执行
|
||
3. 基准测试:测量代码的性能和内存使用
|
||
4. 示例测试:提供函数使用示例并验证输出
|
||
|
||
测试的最佳实践:
|
||
1. 使用描述性的测试名称
|
||
2. 保持测试的独立性
|
||
3. 测试边界条件和错误情况
|
||
4. 使用表驱动测试处理多个用例
|
||
5. 编写清晰的错误消息
|
||
6. 保持高的测试覆盖率
|
||
7. 定期重构和维护测试代码
|
||
|
||
注意事项:
|
||
1. 测试文件必须以 _test.go 结尾
|
||
2. 测试函数必须以 Test 开头
|
||
3. 基准测试函数必须以 Benchmark 开头
|
||
4. 示例测试函数必须以 Example 开头
|
||
5. 使用 t.Helper() 标记辅助函数
|
||
6. 合理使用并行测试提高效率
|
||
*/
|