You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go测试:如何用虚拟目录结构测试目录复制逻辑?

如何为目录复制函数编写基于桩(Stub)的单元测试

核心思路是将依赖的文件系统操作抽象为接口,这样在测试时可以用模拟实现替换真实的file包调用,完全避免操作真实文件。以下是具体步骤和代码示例(以Go语言为例,思路适配大多数静态类型语言):

1. 抽象文件系统操作接口

先把复制函数用到的file包方法(比如MatchFiles、判断目录、复制文件、创建目录)封装成一个接口,让复制函数依赖这个接口而非直接依赖file包:

// FileSystem 定义文件系统操作的抽象接口
type FileSystem interface {
    MatchFiles(path string) ([]string, error) // 获取路径下的所有条目
    IsDir(path string) bool                   // 判断是否为目录
    CopyFile(src, dst string) error           // 复制文件
    MkdirAll(path string) error               // 递归创建目录
}

2. 实现生产环境用的真实文件系统适配器

把真实的file包方法包装成接口的实现,供正常业务逻辑调用:

// RealFileSystem 对接真实file包的实现
type RealFileSystem struct{}

func (r *RealFileSystem) MatchFiles(path string) ([]string, error) {
    return file.MatchFiles(path)
}

func (r *RealFileSystem) IsDir(path string) bool {
    return file.IsDir(path)
}

func (r *RealFileSystem) CopyFile(src, dst string) error {
    // 调用真实的文件复制逻辑,比如file.Copy(src, dst)
    return file.Copy(src, dst)
}

func (r *RealFileSystem) MkdirAll(path string) error {
    return file.MkdirAll(path, 0755)
}

3. 修改复制函数,依赖抽象接口

调整你的递归复制函数,让它接收FileSystem接口作为参数,不再直接调用file包:

func CopyDir(src, dst string, fs FileSystem) error {
    entries, err := fs.MatchFiles(src)
    if err != nil {
        return err
    }

    // 先创建目标目录
    if err := fs.MkdirAll(dst); err != nil {
        return err
    }

    for _, entry := range entries {
        srcPath := filepath.Join(src, entry)
        dstPath := filepath.Join(dst, entry)

        if fs.IsDir(srcPath) {
            // 递归复制子目录
            if err := CopyDir(srcPath, dstPath, fs); err != nil {
                return err
            }
        } else {
            // 复制文件
            if err := fs.CopyFile(srcPath, dstPath); err != nil {
                return err
            }
        }
    }
    return nil
}

4. 编写测试用的模拟(桩)文件系统

创建一个模拟实现,用来预设MatchFiles的返回值、判断目录的结果,同时记录所有文件操作的调用情况,方便后续验证:

// MockFileSystem 测试用的模拟文件系统
type MockFileSystem struct {
    matchFilesFunc func(path string) ([]string, error) // 自定义MatchFiles的返回
    isDirFunc      func(path string) bool              // 自定义IsDir的返回
    copyFileCalls  [][2]string                         // 记录CopyFile的调用参数(src, dst)
    mkdirAllCalls  []string                            // 记录MkdirAll的调用路径
}

func (m *MockFileSystem) MatchFiles(path string) ([]string, error) {
    if m.matchFilesFunc != nil {
        return m.matchFilesFunc(path)
    }
    return nil, nil
}

func (m *MockFileSystem) IsDir(path string) bool {
    if m.isDirFunc != nil {
        return m.isDirFunc(path)
    }
    return false
}

func (m *MockFileSystem) CopyFile(src, dst string) error {
    m.copyFileCalls = append(m.copyFileCalls, [2]string{src, dst})
    return nil // 测试时无需真实复制,只记录调用
}

func (m *MockFileSystem) MkdirAll(path string) error {
    m.mkdirAllCalls = append(m.mkdirAllCalls, path)
    return nil // 测试时无需真实创建目录,只记录调用
}

5. 编写单元测试用例

通过配置Mock的行为,模拟不同的目录结构,验证复制逻辑是否正确:

import (
    "reflect"
    "testing"
)

func TestCopyDir_RecursiveStructure(t *testing.T) {
    mockFS := &MockFileSystem{}

    // 预设MatchFiles的返回:模拟/src下有file1.txt和subdir,/src/subdir下有file2.txt
    mockFS.matchFilesFunc = func(path string) ([]string, error) {
        switch path {
        case "/src":
            return []string{"file1.txt", "subdir"}, nil
        case "/src/subdir":
            return []string{"file2.txt"}, nil
        default:
            return nil, nil
        }
    }

    // 预设IsDir的返回:只有/src/subdir是目录
    mockFS.isDirFunc = func(path string) bool {
        return path == "/src/subdir"
    }

    // 调用复制函数
    err := CopyDir("/src", "/dst", mockFS)
    if err != nil {
        t.Fatalf("CopyDir failed unexpectedly: %v", err)
    }

    // 验证目录创建是否符合预期
    expectedMkdirs := []string{"/dst", "/dst/subdir"}
    if !reflect.DeepEqual(mockFS.mkdirAllCalls, expectedMkdirs) {
        t.Errorf("Expected mkdir calls: %v, got: %v", expectedMkdirs, mockFS.mkdirAllCalls)
    }

    // 验证文件复制是否符合预期
    expectedCopies := [][2]string{
        {"/src/file1.txt", "/dst/file1.txt"},
        {"/src/subdir/file2.txt", "/dst/subdir/file2.txt"},
    }
    if !reflect.DeepEqual(mockFS.copyFileCalls, expectedCopies) {
        t.Errorf("Expected copy calls: %v, got: %v", expectedCopies, mockFS.copyFileCalls)
    }
}

额外测试场景建议

  • 测试空目录:MatchFiles返回空数组,验证是否只创建目标目录,无文件复制调用
  • 测试错误场景:让MatchFiles返回错误,验证复制函数是否正确传递错误
  • 测试单文件复制:源路径是单个文件,验证是否直接调用CopyFile

内容的提问来源于stack exchange,提问作者raviabhiram

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 08:56:05