go - 测试删除目录的最佳实践是什么?

标签 go testing

我有一个我正在制作的 cli,它更多地用于学习目的和创建我自己的 cli,它确实有用。无论如何,我正在测试 delete功能,它工作正常,给了我正确的答案。但是,我不认为这是最佳做法,并且想知道您是否可以让我知道它是否可以。
测试文件

func TestDeleteConfig(t *testing.T) {
    err := cm.DeleteConfig()
    if err != nil {
        t.Errorf("error when deleting the folder: %s", err)
    }

    usr, err := user.Current()
    if err != nil {
        t.Errorf("error when getting user current: %s", err)
    }

    fp := filepath.Join(usr.HomeDir, ".config", "godot", "config.json")
    fmt.Println("the path of the config file", fp)

    if _, e := os.Stat(fp); !os.IsNotExist(e) {
        t.Errorf("error path still exists: %v", e)
    }
}
正在测试的功能
func DeleteConfig() error {

    usr, err := user.Current()
    if err != nil {
        return err
    }

    err = os.RemoveAll(filepath.Join(usr.HomeDir, ".config", "godot"))
    if err != nil {
        return err
    }

    return nil
}
问题是我不想要 DeleteConfig()接受任何论据,因为这是一条艰难的道路。我有一个单独的函数用于删除单个文件,其中的帮助也将解决该问题func DeleteFile(p string) error {} .
因此,出于测试目的,我应该只创建一个 foo目录位于单独的路径(在测试中),删除所述目录,并假设它是否适用于 foo那么它应该与 godot 一起使用目录?

最佳答案

这有点哲学。
如果您绝对不想 stub /模拟代码的任何部分来代替访问真实文件系统进行测试,那么我们正在谈论您所说的系统或集成测试。这本身很好:例如,您可以在作为 CI 管道的一部分的一次性容器中运行此类测试。
但是使用这种方法,您无法明智地进行所谓的单元测试。
要对您的功能进行单元测试,您需要用“虚拟化”的东西替换它使用的东西。究竟如何做到这一点是一个悬而未决的问题。
方法:make可以覆盖os.RemoveAll例如,您可以拥有一个包含“删除整个目录”功能的私有(private)全局变量,如下所示:

var removeAll = os.RemoveAll

func DeleteConfig() error {

    usr, err := user.Current()
    if err != nil {
        return err
    }

    err = removeAll(filepath.Join(usr.HomeDir, ".config", "godot"))
    if err != nil {
        return err
    }

    return nil
}
然后为了测试它,您只需使用您自己的实现对函数进行猴子补丁,该实现将具有与 os.RemoveAll 相同的签名。 , 像这样:
func TestDeleteConfig(t *testing.T) {
  var actualPath string

  removeAll = func(path string) {
    actualPath = path
    return nil
  }
  defer func() { removeAll = os.RemoveAll }()

  DeleteConfig()

  if actualPath != expectedPath {
    t.Errorf("unexpected path: want %s, got %s", expectedPath, actualPath)
  }
}
这种方法还允许测试您的函数如何处理错误:只需将其替换为产生错误的内容,然后检查您的被测函数是否返回了该错误(或以预期的方式包装它,无论如何)。
尽管如此,由于多种原因,它的技术含量有点低:
  • 涉及一个全局变量,因此您必须确保没有两个测试同时运行猴子补丁,或者在运行这些测试之前完成所有补丁。
  • 如果不同的测试需要将其设置为不同的值,则必须对其进行序列化。

  • 方法:不要硬编码“用户”或“配置”的概念
    另一种方法是注意,基本上测试的问题源于您硬编码获取用户的事实。
    撇开您为获取配置位置而采取的有缺陷的方法(您应该使用 something which implements the XDG spec ),如果您可以轻松覆盖获取“根”目录(这是您的代码中用户的主目录),您可以轻松地将您的函数定位为对调用 io/ioutil.TempDir 的结果进行操作.
    所以可能的一种方法是拥有一个接口(interface)类型
    type ConfigStore interface {
      Dir() string
    }
    
    其中Dir()方法应该返回配置存储的根目录的路径。
    您的 DeleteConfig()然后将开始接受 ConfigStore 类型的单个参数,并且在您的程序中,您将拥有它的具体实现,并且在您的测试代码中 - 一个实现相同接口(interface)并管理临时目录的 stub 。
    方法:全面虚拟化
    现在,正在对 bringing filesystem virtualization right into the Go standard library 进行一项工作。 ,但是虽然它还没有,但可以做到这一点的 3rd-party 包已经存在了很长时间,例如, github.com/spf13/afero .
    基本上,它们允许您不使用 os直接但以某种方式编写所有代码,而不是 os包它调用实现特定接口(interface)的类型实例上的方法:在生产代码中,该对象是 os 的薄垫片包,并在测试代码中替换为您想要的任何内容; afero有一个现成的内存 FS 后端 to do this .

    关于go - 测试删除目录的最佳实践是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64650931/

    相关文章:

    go - 复杂结构的实例化

    go - 如何在 go 中制作父结构的映射?

    google-app-engine - gcloud app deploy 尝试编译不需要的文件,我可以排除部分树吗?

    php - 模拟在方法中间创建的对象

    http - 从 http.Request 获取客户端 IP 地址的正确方法

    ssl - Artifactory jfrog cli 无法进行身份验证

    python - 解决 channel /异步测试用例上的 django 测试 RuntimeError

    Angular - 如何正确测试在 Observable 订阅 block 中执行的操作?

    ruby-on-rails - 当记录似乎是唯一的时,为什么我得到 'ActiveRecord::RecordNotUnique:'?

    scala - scalatest 中的 assertEquals