Appearance
单元测试
1. 概述
单元测试是软件测试的基础,它测试代码中的最小可测试单元,如函数、方法或结构体。在Go语言中,单元测试是一种内置的特性,通过 testing 包提供支持。掌握单元测试的方法和技巧,对于编写高质量、可靠的Go代码至关重要。
单元测试的主要目标是验证代码的功能是否符合预期,确保代码在各种情况下都能正确工作。通过单元测试,可以及早发现和修复代码中的问题,提高代码的可维护性和可扩展性。
2. 基本概念
2.1 语法
Go语言单元测试的基本语法包括:
- 测试函数:以
Test开头的函数,接受一个*testing.T类型的参数 - 测试文件:以
_test.go结尾的文件,包含测试函数 - 测试表:使用表格形式组织测试用例,提高测试的可读性和可维护性
- 子测试:使用
t.Run方法创建子测试,实现更细粒度的测试 - 测试断言:用于验证测试结果是否符合预期
2.2 语义
单元测试的核心语义包括:
- 隔离性:测试应该独立运行,不依赖于外部环境或其他测试的结果
- 可重复性:测试应该在任何时候运行都能产生相同的结果
- 覆盖性:测试应该覆盖所有重要的代码路径和边界情况
- 简洁性:测试代码应该简洁明了,易于理解和维护
- 快速性:测试应该运行快速,便于频繁执行
2.3 规范
单元测试的最佳实践规范:
- 每个测试函数应该测试一个特定的功能
- 使用测试表组织测试用例,提高测试的可读性和可维护性
- 确保测试覆盖所有重要的代码路径和边界情况
- 使用模拟(mock)或桩(stub)来隔离外部依赖
- 测试代码应该与被测试代码分离,放在单独的测试文件中
3. 原理深度解析
3.1 单元测试的工作原理
Go语言单元测试的工作原理:
- 测试发现:Go测试工具会自动发现和执行以
Test开头的函数 - 测试执行:测试工具会为每个测试函数创建一个单独的goroutine执行
- 测试报告:测试工具会收集测试结果,生成测试报告
- 测试覆盖率:测试工具会跟踪测试代码对被测试代码的覆盖情况
3.2 测试函数的执行流程
测试函数的执行流程:
- 初始化:测试工具创建
*testing.T实例,传递给测试函数 - 执行:测试函数执行测试逻辑,调用
*testing.T的方法报告测试结果 - 失败处理:当测试失败时,测试函数会停止执行该测试用例
- 清理:测试函数执行完成后,测试工具会清理测试环境
3.3 测试表的工作原理
测试表的工作原理:
- 定义测试表:使用结构体切片定义测试用例,每个结构体包含输入和预期输出
- 遍历测试表:使用循环遍历测试表中的每个测试用例
- 执行测试:对每个测试用例执行测试逻辑,验证结果
- 报告结果:根据测试结果报告测试成功或失败
4. 常见错误与踩坑点
4.1 测试函数命名错误
错误表现:测试函数不被执行 产生原因:测试函数的命名不符合规范,没有以 Test 开头 解决方案:确保测试函数以 Test 开头,并且函数名的首字母大写 示例代码:
go
// 错误示例
func testAdd(t *testing.T) { // 不会被执行
// 测试代码
}
// 正确示例
func TestAdd(t *testing.T) { // 会被执行
// 测试代码
}4.2 测试依赖外部环境
错误表现:测试在不同环境下结果不一致 产生原因:测试依赖于外部环境,如网络、数据库等 解决方案:使用模拟(mock)或桩(stub)来隔离外部依赖 示例代码:
go
// 错误示例(依赖外部API)
func TestGetUser(t *testing.T) {
user, err := getUserFromAPI(123)
if err != nil {
t.Fatalf("Error getting user: %v", err)
}
if user.ID != 123 {
t.Errorf("Expected user ID 123, got %d", user.ID)
}
}
// 正确示例(使用mock)
func TestGetUser(t *testing.T) {
mockAPI := &MockAPI{}
user, err := mockAPI.GetUser(123)
if err != nil {
t.Fatalf("Error getting user: %v", err)
}
if user.ID != 123 {
t.Errorf("Expected user ID 123, got %d", user.ID)
}
}4.3 测试覆盖不全面
错误表现:测试覆盖率低,未覆盖重要的代码路径 产生原因:测试用例不全面,没有覆盖边界情况和异常情况 解决方案:增加测试用例,覆盖更多的代码路径和边界情况 示例代码:
go
// 错误示例(覆盖率低)
func TestAdd(t *testing.T) {
result := Add(1, 2)
if result != 3 {
t.Errorf("Expected 3, got %d", result)
}
}
// 正确示例(覆盖率高)
func TestAdd(t *testing.T) {
testCases := []struct {
a, b, expected int
}{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
{-1, -2, -3},
{math.MaxInt32, 1, math.MaxInt32 + 1},
}
for _, tc := range testCases {
result := Add(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Add(%d, %d) = %d, expected %d", tc.a, tc.b, result, tc.expected)
}
}
}4.4 测试过于复杂
错误表现:测试代码难以理解和维护 产生原因:测试函数过于复杂,测试多个功能 解决方案:将复杂的测试拆分为多个简单的测试函数,每个函数测试一个特定的功能 示例代码:
go
// 错误示例(测试多个功能)
func TestAddAndSubtract(t *testing.T) {
// 测试加法
result := Add(1, 2)
if result != 3 {
t.Errorf("Expected 3, got %d", result)
}
// 测试减法
result = Subtract(5, 2)
if result != 3 {
t.Errorf("Expected 3, got %d", result)
}
}
// 正确示例(拆分测试)
func TestAdd(t *testing.T) {
result := Add(1, 2)
if result != 3 {
t.Errorf("Expected 3, got %d", result)
}
}
func TestSubtract(t *testing.T) {
result := Subtract(5, 2)
if result != 3 {
t.Errorf("Expected 3, got %d", result)
}
}4.5 测试之间相互依赖
错误表现:测试结果依赖于测试的执行顺序 产生原因:测试之间共享状态,导致测试结果相互影响 解决方案:确保测试之间相互独立,不共享状态 示例代码:
go
// 错误示例(测试之间共享状态)
var counter int
func TestIncrement(t *testing.T) {
counter++
if counter != 1 {
t.Errorf("Expected 1, got %d", counter)
}
}
func TestDecrement(t *testing.T) {
counter--
if counter != 0 {
t.Errorf("Expected 0, got %d", counter)
}
}
// 正确示例(测试之间相互独立)
func TestIncrement(t *testing.T) {
counter := 0
counter++
if counter != 1 {
t.Errorf("Expected 1, got %d", counter)
}
}
func TestDecrement(t *testing.T) {
counter := 1
counter--
if counter != 0 {
t.Errorf("Expected 0, got %d", counter)
}
}5. 常见应用场景
5.1 函数测试
场景描述:测试单个函数的功能是否符合预期 使用方法:编写测试函数,验证函数的输入和输出 示例代码:
go
// 被测试函数
func Add(a, b int) int {
return a + b
}
// 测试函数
func TestAdd(t *testing.T) {
testCases := []struct {
a, b, expected int
}{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
{-1, -2, -3},
}
for _, tc := range testCases {
result := Add(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Add(%d, %d) = %d, expected %d", tc.a, tc.b, result, tc.expected)
}
}
}5.2 方法测试
场景描述:测试结构体方法的功能是否符合预期 使用方法:创建结构体实例,调用方法,验证结果 示例代码:
go
// 被测试结构体
type Calculator struct {
name string
}
// 被测试方法
func (c *Calculator) Add(a, b int) int {
return a + b
}
// 测试函数
func TestCalculator_Add(t *testing.T) {
calc := &Calculator{name: "Test Calculator"}
testCases := []struct {
a, b, expected int
}{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
}
for _, tc := range testCases {
result := calc.Add(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Calculator.Add(%d, %d) = %d, expected %d", tc.a, tc.b, result, tc.expected)
}
}
}5.3 错误处理测试
场景描述:测试函数的错误处理是否正确 使用方法:测试函数在各种情况下的错误返回 示例代码:
go
// 被测试函数
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// 测试函数
func TestDivide(t *testing.T) {
testCases := []struct {
a, b int
expected int
expectedErr bool
}{
{6, 3, 2, false},
{0, 1, 0, false},
{5, 0, 0, true},
}
for _, tc := range testCases {
result, err := Divide(tc.a, tc.b)
if tc.expectedErr {
if err == nil {
t.Errorf("Divide(%d, %d) expected error, got nil", tc.a, tc.b)
}
} else {
if err != nil {
t.Errorf("Divide(%d, %d) unexpected error: %v", tc.a, tc.b, err)
}
if result != tc.expected {
t.Errorf("Divide(%d, %d) = %d, expected %d", tc.a, tc.b, result, tc.expected)
}
}
}
}5.4 边界情况测试
场景描述:测试函数在边界情况下的行为 使用方法:测试函数在边界输入时的表现 示例代码:
go
// 被测试函数
func Max(a, b int) int {
if a > b {
return a
}
return b
}
// 测试函数
func TestMax(t *testing.T) {
testCases := []struct {
a, b, expected int
}{
{1, 2, 2},
{2, 1, 2},
{0, 0, 0},
{-1, -2, -1},
{math.MaxInt32, math.MinInt32, math.MaxInt32},
}
for _, tc := range testCases {
result := Max(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Max(%d, %d) = %d, expected %d", tc.a, tc.b, result, tc.expected)
}
}
}5.5 子测试
场景描述:测试复杂功能的不同方面 使用方法:使用 t.Run 创建子测试 示例代码:
go
// 被测试结构体
type User struct {
ID int
Name string
}
// 被测试方法
func (u *User) Validate() error {
if u.ID <= 0 {
return errors.New("invalid ID")
}
if u.Name == "" {
return errors.New("invalid name")
}
return nil
}
// 测试函数
func TestUser_Validate(t *testing.T) {
t.Run("ValidUser", func(t *testing.T) {
user := &User{ID: 1, Name: "John"}
if err := user.Validate(); err != nil {
t.Errorf("Expected no error, got %v", err)
}
})
t.Run("InvalidID", func(t *testing.T) {
user := &User{ID: 0, Name: "John"}
if err := user.Validate(); err == nil {
t.Errorf("Expected error for invalid ID, got nil")
}
})
t.Run("InvalidName", func(t *testing.T) {
user := &User{ID: 1, Name: ""}
if err := user.Validate(); err == nil {
t.Errorf("Expected error for invalid name, got nil")
}
})
}6. 企业级进阶应用场景
6.1 测试驱动开发 (TDD)
场景描述:使用测试驱动开发方法开发代码 使用方法:先编写测试,再编写实现代码,最后运行测试验证 示例代码:
go
// 1. 先编写测试
func TestCalculateTotal(t *testing.T) {
items := []Item{
{Price: 10, Quantity: 2},
{Price: 5, Quantity: 3},
}
expected := 35
result := CalculateTotal(items)
if result != expected {
t.Errorf("Expected %d, got %d", expected, result)
}
}
// 2. 编写实现代码
func CalculateTotal(items []Item) int {
total := 0
for _, item := range items {
total += item.Price * item.Quantity
}
return total
}
// 3. 运行测试验证6.2 模拟外部依赖
场景描述:测试依赖外部服务的代码 使用方法:使用模拟(mock)或桩(stub)来隔离外部依赖 示例代码:
go
// 接口定义
type APIClient interface {
GetUser(id int) (*User, error)
}
// 模拟实现
type MockAPIClient struct {
users map[int]*User
err error
}
func (m *MockAPIClient) GetUser(id int) (*User, error) {
if m.err != nil {
return nil, m.err
}
return m.users[id], nil
}
// 测试函数
func TestUserService_GetUser(t *testing.T) {
// 创建模拟API客户端
mockClient := &MockAPIClient{
users: map[int]*User{
123: {ID: 123, Name: "John"},
},
}
// 创建用户服务
userService := NewUserService(mockClient)
// 测试获取用户
user, err := userService.GetUser(123)
if err != nil {
t.Fatalf("Error getting user: %v", err)
}
if user.ID != 123 {
t.Errorf("Expected user ID 123, got %d", user.ID)
}
if user.Name != "John" {
t.Errorf("Expected name John, got %s", user.Name)
}
}6.3 测试覆盖率分析
场景描述:分析测试代码对被测试代码的覆盖程度 使用方法:使用 go test -cover 命令分析测试覆盖率 示例代码:
bash
# 运行测试并分析覆盖率
go test -cover ./...
# 生成覆盖率报告
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out6.4 表驱动测试
场景描述:使用表驱动测试方法测试多个场景 使用方法:使用测试表组织测试用例,提高测试的可读性和可维护性 示例代码:
go
func TestAdd(t *testing.T) {
testCases := []struct {
name string
a, b int
expected int
}{
{"Positive numbers", 1, 2, 3},
{"Zero values", 0, 0, 0},
{"Negative numbers", -1, -2, -3},
{"Mixed signs", -1, 2, 1},
{"Maximum values", math.MaxInt32, 1, math.MaxInt32 + 1},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := Add(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Add(%d, %d) = %d, expected %d", tc.a, tc.b, result, tc.expected)
}
})
}
}6.5 持续集成测试
场景描述:在持续集成环境中自动运行测试 使用方法:配置CI/CD pipeline,自动运行测试 示例代码:
yaml
# CI/CD配置
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.20
- name: Run tests
run: go test -v ./...
- name: Run tests with coverage
run: go test -cover ./...7. 行业最佳实践
7.1 测试命名规范
实践内容:使用清晰、描述性的测试函数名 推荐理由:清晰的测试函数名可以提高测试的可读性和可维护性 示例:
TestAdd- 测试加法函数TestUser_Validate- 测试User结构体的Validate方法TestCalculator_Add- 测试Calculator结构体的Add方法
7.2 测试表模式
实践内容:使用测试表组织测试用例 推荐理由:测试表可以提高测试的可读性和可维护性,便于添加新的测试用例 示例代码:
go
func TestAdd(t *testing.T) {
testCases := []struct {
name string
a, b int
expected int
}{
{"Positive numbers", 1, 2, 3},
{"Zero values", 0, 0, 0},
{"Negative numbers", -1, -2, -3},
{"Mixed signs", -1, 2, 1},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := Add(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Add(%d, %d) = %d, expected %d", tc.a, tc.b, result, tc.expected)
}
})
}
}7.3 测试隔离
实践内容:确保测试之间相互独立 推荐理由:独立的测试可以提高测试的可靠性和可维护性,避免测试之间的相互影响 示例代码:
go
func TestAdd(t *testing.T) {
// 每个测试用例都是独立的
testCases := []struct {
a, b, expected int
}{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
}
for _, tc := range testCases {
result := Add(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Add(%d, %d) = %d, expected %d", tc.a, tc.b, result, tc.expected)
}
}
}7.4 测试边界情况
实践内容:测试边界情况和异常情况 推荐理由:边界情况和异常情况是最容易出错的地方,测试这些情况可以提高代码的健壮性 示例代码:
go
func TestDivide(t *testing.T) {
testCases := []struct {
a, b int
expected int
expectedErr bool
}{
{6, 3, 2, false},
{0, 1, 0, false},
{5, 0, 0, true}, // 边界情况:除数为零
{math.MaxInt32, 1, math.MaxInt32, false}, // 边界情况:最大值
{math.MinInt32, -1, math.MaxInt32, false}, // 边界情况:最小值
}
for _, tc := range testCases {
result, err := Divide(tc.a, tc.b)
if tc.expectedErr {
if err == nil {
t.Errorf("Divide(%d, %d) expected error, got nil", tc.a, tc.b)
}
} else {
if err != nil {
t.Errorf("Divide(%d, %d) unexpected error: %v", tc.a, tc.b, err)
}
if result != tc.expected {
t.Errorf("Divide(%d, %d) = %d, expected %d", tc.a, tc.b, result, tc.expected)
}
}
}
}7.5 测试文档
实践内容:为测试编写清晰的文档和注释 推荐理由:清晰的文档和注释可以提高测试的可读性和可维护性,便于其他开发者理解测试的目的和方法 示例代码:
go
// TestAdd tests the Add function with various inputs
// It covers:
// - Positive numbers
// - Zero values
// - Negative numbers
// - Mixed signs
// - Boundary values
func TestAdd(t *testing.T) {
// 测试代码
}7.6 模拟和桩
实践内容:使用模拟和桩来隔离外部依赖 推荐理由:模拟和桩可以提高测试的可靠性和可重复性,避免依赖外部环境 示例代码:
go
// 接口定义
type APIClient interface {
GetUser(id int) (*User, error)
}
// 模拟实现
type MockAPIClient struct {
users map[int]*User
err error
}
func (m *MockAPIClient) GetUser(id int) (*User, error) {
if m.err != nil {
return nil, m.err
}
return m.users[id], nil
}
// 测试函数
func TestUserService_GetUser(t *testing.T) {
mockClient := &MockAPIClient{
users: map[int]*User{
123: {ID: 123, Name: "John"},
},
}
userService := NewUserService(mockClient)
user, err := userService.GetUser(123)
if err != nil {
t.Fatalf("Error getting user: %v", err)
}
if user.ID != 123 {
t.Errorf("Expected user ID 123, got %d", user.ID)
}
}8. 常见问题答疑(FAQ)
8.1 如何编写单元测试?
问题描述:如何编写Go语言的单元测试? 回答内容:创建以 _test.go 结尾的测试文件,编写以 Test 开头的测试函数,使用 *testing.T 来报告测试结果 示例代码:
go
// add_test.go
func TestAdd(t *testing.T) {
result := Add(1, 2)
if result != 3 {
t.Errorf("Expected 3, got %d", result)
}
}8.2 如何使用测试表?
问题描述:如何使用测试表组织测试用例? 回答内容:使用结构体切片定义测试用例,每个结构体包含输入和预期输出,然后遍历测试表执行测试 示例代码:
go
func TestAdd(t *testing.T) {
testCases := []struct {
a, b, expected int
}{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
}
for _, tc := range testCases {
result := Add(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Add(%d, %d) = %d, expected %d", tc.a, tc.b, result, tc.expected)
}
}
}8.3 如何创建子测试?
问题描述:如何创建子测试? 回答内容:使用 t.Run 方法创建子测试,实现更细粒度的测试 示例代码:
go
func TestUser_Validate(t *testing.T) {
t.Run("ValidUser", func(t *testing.T) {
user := &User{ID: 1, Name: "John"}
if err := user.Validate(); err != nil {
t.Errorf("Expected no error, got %v", err)
}
})
t.Run("InvalidID", func(t *testing.T) {
user := &User{ID: 0, Name: "John"}
if err := user.Validate(); err == nil {
t.Errorf("Expected error for invalid ID, got nil")
}
})
}8.4 如何模拟外部依赖?
问题描述:如何模拟外部依赖进行测试? 回答内容:使用接口和模拟实现来隔离外部依赖 示例代码:
go
// 接口定义
type APIClient interface {
GetUser(id int) (*User, error)
}
// 模拟实现
type MockAPIClient struct {
users map[int]*User
err error
}
func (m *MockAPIClient) GetUser(id int) (*User, error) {
if m.err != nil {
return nil, m.err
}
return m.users[id], nil
}
// 测试函数
func TestUserService_GetUser(t *testing.T) {
mockClient := &MockAPIClient{
users: map[int]*User{
123: {ID: 123, Name: "John"},
},
}
userService := NewUserService(mockClient)
user, err := userService.GetUser(123)
if err != nil {
t.Fatalf("Error getting user: %v", err)
}
if user.ID != 123 {
t.Errorf("Expected user ID 123, got %d", user.ID)
}
}8.5 如何分析测试覆盖率?
问题描述:如何分析测试覆盖率? 回答内容:使用 go test -cover 命令分析测试覆盖率,使用 go tool cover 生成覆盖率报告 示例代码:
bash
# 运行测试并分析覆盖率
go test -cover ./...
# 生成覆盖率报告
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out8.6 如何运行特定的测试?
问题描述:如何运行特定的测试函数或子测试? 回答内容:使用 -run 标志指定要运行的测试函数或子测试 示例代码:
bash
# 运行特定的测试函数
go test -run TestAdd
# 运行特定的子测试
go test -run TestUser_Validate/ValidUser9. 实战练习
9.1 基础练习
练习题目:编写单元测试测试加法函数 解题思路:创建测试文件,编写测试函数,使用测试表组织测试用例 常见误区:测试函数命名错误,测试用例不全面 分步提示:
- 创建
add_test.go文件 - 编写
TestAdd函数 - 使用测试表组织测试用例,包括正数、负数、零值等情况
- 运行测试验证 参考代码:
go
// add.go
func Add(a, b int) int {
return a + b
}
// add_test.go
func TestAdd(t *testing.T) {
testCases := []struct {
a, b, expected int
}{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
{-1, -2, -3},
{100, 200, 300},
}
for _, tc := range testCases {
result := Add(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Add(%d, %d) = %d, expected %d", tc.a, tc.b, result, tc.expected)
}
}
}9.2 进阶练习
练习题目:编写单元测试测试用户验证功能 解题思路:创建测试文件,编写测试函数,使用子测试测试不同的验证场景 常见误区:测试覆盖不全面,没有测试边界情况 分步提示:
- 创建
user_test.go文件 - 编写
TestUser_Validate函数 - 使用子测试测试有效用户、无效ID、无效名称等情况
- 运行测试验证 参考代码:
go
// user.go
type User struct {
ID int
Name string
}
func (u *User) Validate() error {
if u.ID <= 0 {
return errors.New("invalid ID")
}
if u.Name == "" {
return errors.New("invalid name")
}
return nil
}
// user_test.go
func TestUser_Validate(t *testing.T) {
t.Run("ValidUser", func(t *testing.T) {
user := &User{ID: 1, Name: "John"}
if err := user.Validate(); err != nil {
t.Errorf("Expected no error, got %v", err)
}
})
t.Run("InvalidID", func(t *testing.T) {
user := &User{ID: 0, Name: "John"}
if err := user.Validate(); err == nil {
t.Errorf("Expected error for invalid ID, got nil")
}
})
t.Run("InvalidName", func(t *testing.T) {
user := &User{ID: 1, Name: ""}
if err := user.Validate(); err == nil {
t.Errorf("Expected error for invalid name, got nil")
}
})
t.Run("InvalidBoth", func(t *testing.T) {
user := &User{ID: 0, Name: ""}
if err := user.Validate(); err == nil {
t.Errorf("Expected error for invalid ID and name, got nil")
}
})
}9.3 挑战练习
练习题目:编写单元测试测试除法函数,包括错误处理 解题思路:创建测试文件,编写测试函数,测试正常情况和错误情况 常见误区:没有测试错误处理,测试覆盖不全面 分步提示:
- 创建
divide_test.go文件 - 编写
TestDivide函数 - 测试正常情况和除数为零的错误情况
- 运行测试验证 参考代码:
go
// divide.go
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// divide_test.go
func TestDivide(t *testing.T) {
testCases := []struct {
a, b int
expected int
expectedErr bool
}{
{6, 3, 2, false},
{0, 1, 0, false},
{5, 0, 0, true},
{10, 2, 5, false},
{7, 3, 2, false}, // 整数除法
}
for _, tc := range testCases {
result, err := Divide(tc.a, tc.b)
if tc.expectedErr {
if err == nil {
t.Errorf("Divide(%d, %d) expected error, got nil", tc.a, tc.b)
}
} else {
if err != nil {
t.Errorf("Divide(%d, %d) unexpected error: %v", tc.a, tc.b, err)
}
if result != tc.expected {
t.Errorf("Divide(%d, %d) = %d, expected %d", tc.a, tc.b, result, tc.expected)
}
}
}
}10. 知识点总结
10.1 核心要点
- 单元测试是软件测试的基础,测试代码中的最小可测试单元
- Go语言提供了内置的测试框架,通过
testing包支持单元测试 - 测试函数必须以
Test开头,接受一个*testing.T类型的参数 - 使用测试表组织测试用例,提高测试的可读性和可维护性
- 使用子测试实现更细粒度的测试
- 使用模拟(mock)或桩(stub)来隔离外部依赖
- 分析测试覆盖率,确保测试覆盖足够的代码
10.2 易错点回顾
- 测试函数命名错误,没有以
Test开头 - 测试依赖外部环境,导致测试结果不一致
- 测试覆盖不全面,未覆盖重要的代码路径
- 测试过于复杂,难以理解和维护
- 测试之间相互依赖,导致测试结果不可靠
- 没有测试边界情况和异常情况
11. 拓展参考资料
11.1 官方文档链接
11.2 进阶学习路径建议
- 学习测试驱动开发 (TDD) 方法
- 掌握模拟和桩的使用技巧
- 学习集成测试和端到端测试
- 掌握基准测试和性能测试
- 学习持续集成和持续测试
