Go语言含channel、目录遍历、API调用的程序如何编写单元测试
代码改造与单元测试方案
1. 代码结构调整(仅小改动,不影响原有运行逻辑)
当前代码难以测试的核心问题是全局依赖过多、错误处理耦合了panic、第三方依赖未做抽象,仅需做以下几处小调整即可:
- 改造
WalkFilePath函数,移除全局依赖,改为参数注入,同时将内部panic改为错误返回:
原有main函数调用时仍传入全局的// 改造后的函数签名,原有逻辑不变,仅把全局的rootpath、paths改为入参,错误对外返回 func WalkFilePath(root string, paths chan<- string) error { return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return fmt.Errorf("Failed to walk directory: %T %w", err, err) } if !info.IsDir() { paths <- path } return nil }) }*rootpath和全局paths变量,收到错误后再panic即可,原有功能完全不受影响。 - 移除全局的
paths变量,在main函数内部声明即可,放开你注释掉的paths := make(chan string)代码即可。 - 抽象
putio.Client的上传能力为接口,避免测试时发起真实上传请求:// 定义接口,putio原生客户端天然实现该接口 type Uploader interface { Upload(ctx context.Context, f *os.File, path string, parentID int64) (*putio.Upload, error) } // worker函数的client参数改为Uploader接口类型,原有逻辑不变 func worker(paths <-chan string, wg *sync.WaitGroup, client Uploader) { // 原有代码完全不变 }
2. WalkFilePath单元测试编写
测试不需要启动goroutine,仅需单独验证目录遍历、文件路径发送逻辑即可:
- 用
testing.T.TempDir()创建临时测试目录,构造预设的文件/目录结构 - 初始化带缓冲的channel,缓冲大小大于测试文件数量即可避免阻塞
- 调用
WalkFilePath传入临时目录与channel,收集channel内的所有路径和预期结果对比
示例测试代码:
package main import ( "path/filepath" "os" "sort" "reflect" "testing" ) func TestWalkFilePath(t *testing.T) { // 构造测试目录结构 tempDir := t.TempDir() testFiles := []string{ "a.txt", "subdir/b.md", "subdir/c.go", } expectedPaths := make([]string, 0, len(testFiles)) for _, f := range testFiles { fullPath := filepath.Join(tempDir, f) if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil { t.Fatalf("create test dir failed: %v", err) } if err := os.WriteFile(fullPath, []byte("test content"), 0644); err != nil { t.Fatalf("create test file failed: %v", err) } expectedPaths = append(expectedPaths, fullPath) } // 调用待测试函数 pathsChan := make(chan string, len(expectedPaths)) err := WalkFilePath(tempDir, pathsChan) close(pathsChan) // 校验错误返回 if err != nil { t.Fatalf("WalkFilePath returned unexpected error: %v", err) } // 收集返回结果对比 gotPaths := make([]string, 0) for p := range pathsChan { gotPaths = append(gotPaths, p) } sort.Strings(expectedPaths) sort.Strings(gotPaths) if !reflect.DeepEqual(expectedPaths, gotPaths) { t.Errorf("WalkFilePath got paths %v, expected %v", gotPaths, expectedPaths) } }
3. worker函数单元测试编写
使用Mock实现模拟上传行为,验证文件读取、上传逻辑是否符合预期:
- 实现Mock上传器,记录调用参数、模拟错误返回
type mockUploader struct { UploadedPaths []string MockErr error } func (m *mockUploader) Upload(ctx context.Context, f *os.File, path string, parentID int64) (*putio.Upload, error) { m.UploadedPaths = append(m.UploadedPaths, path) if m.MockErr != nil { return nil, m.MockErr } return &putio.Upload{File: &putio.File{Name: filepath.Base(path)}}, nil }
- 测试时构造临时测试文件,传入worker的channel,验证Mock调用情况是否符合预期即可,也可以劫持日志输出验证错误、成功日志是否正确打印。
4. 主函数测试
主函数多为初始化逻辑和流程编排,不需要编写单元测试,仅需做简单集成测试验证流程通顺即可。
内容的提问来源于stack exchange,提问作者baris
相关产品推荐
相关产品推荐

