Go 测试提示和技巧

GO 刘宇帅 5年前 阅读量: 4816

原文地址:

https://medium.com/@povilasve/go-advanced-tips-tricks-a872503ac859

这篇文章是基于维尔纽斯Go见面会做的整理。
我看了很多博客并把他们内容整理以下。首先我要感谢哪些收集所有的想法并在社区分享给大家。我这篇文章有用到下面文章的内容和例子:

技巧一:不要用框架

Ben Johnson的技巧。Go本身的测试框架很好用,你可以用Go本身来写测试而且也不依赖任何框架和引擎。也可以看看Ben Johnson的帮助函数,可以帮助你节省不少代码。

技巧二:使用带”_test“的测试包

Ben Johnson的技巧。使用”_test“的包不允许你调用未对外导出的标识符。这把测试放在了单独的包,这样你可以用来测试包对外开放的api非常有用。

避免全局变量

Mitchell Hashimoto的技巧。如果你使用全局常量那么你无法配置或者修改相应变量。例外情况是全局变量是被用来做默认值的情况。看下下面的例子:

// Bad, tests cannot change value!
const port = 8080
// Better, tests can change the value.
var port = 8080
// Even better, tests can configure Port via struct.
const defaultPort = 8080
type AppConfig {
  Port int // set it to defaultPort using constructor.
}

下面是一些技巧希望能让你的测试更好

技巧一:测试套件

这个技巧在标准连接库里别使用到了。我是在Mitchell Hashimoto和 Dave Cheney文章里学到的。Go测试很好的支持从文件中加载数据。首先,Go构建会忽略文件夹testData,第二,Go跑测试的时候把当前目录当作包目录。这允许你使用相对路径从testData目录加载或者存储数据。下面是一个例子:

func helperLoadBytes(t *testing.T, name string) []byte {
  path := filepath.Join("testdata", name) // relative path
  bytes, err := ioutil.ReadFile(path)
  if err != nil {
    t.Fatal(err)
  }
  return bytes
}

技巧二:Golden files

这个技巧也是在标准连接库里被用到,但是我是从Mitchell Hashimoto的文章里学到的。具体方式就是把期待的输出结果保存为以.golden结尾的文件并提供一个参数可以用来标示更新相应文件。下面是一个例子:

var update = flag.Bool("update", false, "update .golden files")
func TestSomething(t *testing.T) {
  actual := doSomething()
  golden := filepath.Join(“testdata”, tc.Name+”.golden”)
  if *update {
    ioutil.WriteFile(golden, actual, 0644)
  }
  expected, _ := ioutil.ReadFile(golden)

  if !bytes.Equal(actual, expected) {
    // FAIL!
  }
}

这个技巧可以让你不用硬编码的方式测试复杂的应用。

技巧三:测试帮助函数

Mitchell Hashimoto的测试技巧。有时候测试代码比较复杂,当你为你的代码准备适当的测试用例的时候,经常需要去处理很多无关的错误检查,例如检查测试文件是否加载,检查传过来的参数是否是json等等... 这样会让代码越来越复杂!为了解决这种问题,我们应该把这些代码拆分到帮助函数里。帮助函数应该永远不要返回error,而是把相应的错误通过testing.T报告具体的错误。
另外,如果你的帮助函数在结束后需要做清理工作,你应该放回一个函数来执行具体的清理。下面是一个例子:

func testChdir(t *testing.T, dir string) func() {
  old, err := os.Getwd()
  if err != nil {
    t.Fatalf("err: %s", err)
  }
  if err := os.Chdir(dir); err != nil {
    t.Fatalf("err: %s", err)
  }
  return func() {
    if err := os.Chdir(old); err != nil {
       t.Fatalf("err: %s", err)
    }
  }
}
func TestThing(t *testing.T) {
  defer testChdir(t, "/other")()
  // ...
}

(注:这个例子来自于Mitchell Hashimoto的文章Advanced Testing with Go)。这个例子里的另外一个技巧是defer的使用。上面的例子使用defer testChdir(t, "/other")()调用testChdir函数并延迟执行清理函数。

技巧四:Subprocessing: Real

有时候你的代码依赖可执行文件,例如你的代码依赖于git。测试这个代码的一个方法是mock git,但是这还是挺难的。第二种方法是调用git可执行文件。但是如果没有安装git怎么跑测试呢?这个技巧就是通过检查系统是否有git否者跳过相应的测试。下面是一个例子:

var testHasGit bool
func init() {
  if _, err := exec.LookPath("git"); err == nil {
    testHasGit = true
  }
}
func TestGitGetter(t *testing.T) {
  if !testHasGit {
    t.Log("git not found, skipping")
    t.Skip()
  }
  // ...
}

(这个例子来自 Mitchell Hashimoto的文章 Advanced Testing with Go

技巧五:Subprocessing: Mock

Andrew Gerrand 和 Mitchell Hashimoto的测试技巧。这个技巧让你在测试代码里mock一个子进程。这个想法在标准库测试里可以看到。假定我们要测试git失败的场景。我们看下下面的例子:

func CrashingGit() {
  os.Exit(1)
}
func TestFailingGit(t *testing.T) {
  if os.Getenv("BE_CRASHING_GIT") == "1" {
    CrashingGit()
    return
  }
  cmd := exec.Command(os.Args[0], "-test.run=TestFailingGit")
  cmd.Env = append(os.Environ(), "BE_CRASHING_GIT=1")
  err := cmd.Run()
  if e, ok := err.(*exec.ExitError); ok && !e.Success() {
    return
  }
  t.Fatalf("Process ran with err %v, want os.Exit(1)", err)
}

这个例子是使用轻微的修改用子程序跑测试框架。轻微的修改是运行同样的命令(-test.run=TestFailingGit part),但是会设一个环境变量BE_CRASHING_GIT=1,这个变量可以用来区分测试是在正常执行还是子进程执行。

技巧六:把mocks、帮助函数放到testing.go文件

Hashimoto给了一个有趣的建议,建议我们把帮助函数、fixtures、stubs exported放到testing.go文件。(注:testing.go是正常的文件,不会被当作测试文件)这样你就可以在不同的包里使用你的mocks和帮助函数,其他人也可以在测试里使用你的代码。

关注慢测试

Peter Bourgon的测试技巧。如果你有比较慢的测试,那么等待他们执行完成很烦人,尤其是你想知道是否可以构建应用的时候。解决办法就是把慢的测试放到_integration_test.go文件中并在文件开头添加标签。例如:

// +build integration

然后你在执行go test就不回去执行这些慢的测试了。如果你想执行这些慢的测试需要添加构建参数:

go test -tags=integration

就我而言,我使用了命令别名用来执行当前目录和子目录但是除了vendor目录外的所有测试

alias gtest="go test \$(go list ./… | grep -v /vendor/) 
-tags=integration"

别名也支持简洁的参数:

$ gtest
...
$ gtest -v
...

谢谢你的阅读!如果你有问题或者给我一些反馈,你可以通过我的博客 https://povilasv.me或者twitter@PofkeVe联系我。

提示

功能待开通!


暂无评论~