[译] Go语言测试进阶版建议与技巧

阅读本篇文章前,你最好已经知道如何写基本的单元测试。本篇文章共包含3个小建议,以及7个小技巧。

建议一,不要使用框架

Go语言自身已经有一个非常棒的测试框架,它允许你使用Go编写测试代码,不需要再额外学习其它的库或测试引擎。关于断言方面的帮助函数,你可以看看这个 testing,或者这个 assert.go :)

建议二,使用"_test"包名

相较于直接使用被测试代码的包名,使用 *_test包名使得测试代码只能访问包中对外暴露出的接口。这使得你在写测试时更多的是站在包使用者的角度来写,从而使得你可以思考包的接口是否设计合理。

建议三,避免全局常量配置项

避免使用全局常量配置项,因为测试代码无法修改常量。下面举了三个例子做对比:

// 1. 不好,测试代码无法修改它
const port = 8080

// 2. 好一些,测试代码可以修改它
var port = 8080

// 3. 更好的方式,测试代码可以通过 struct 配置 Port
const defaultPort = 8080
type AppConfig {
  Port int // 构造函数中初始化为 defaultPort
}

技巧一,加载测试数据

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文件中

将测试时的预期结果保存至.golden文件中。并且提供一个flag来决定是否更新它。使用这个技巧可以避免在测试代码中硬编码预期输出结果非常复杂的内容。看例子:

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!
  }
}

技巧三,测试时的初始化、清理代码

有时候测试代码比较复杂,在跑测试的case之前需要初始化好环境,这可能会包含很多不相关的错误检查,比如测试文件是否加载成功,测试数据是否能按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")()
  // ...
}

上面的例子包含了另外一个关于defer使用的非常酷的技巧。defer testChdir(t, "/other")()会先执行testChdir内的代码,并且在TestThing结束时执行testChdir所返回的清理函数中的代码。

技巧四,当依赖第三方可执行程序时

有时测试代码会依赖第三方可执行程序,我们可以通过以下方法检查程序是否存在,存在则执行测试,不存在则跳过测试。

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()
  }
  // ...
}

技巧五,测试包含os.Exit的代码

该方法通过启动子进程的方式,避免测试包含os.Exit的代码导致测试程序提前退出。看例子:

func CrashingGit() {
  os.Exit(1)
}
func TestFailingGit(t *testing.T) {
  if os.Getenv("BE_CRASHING_GIT") == "1" { // 子进程进入这个逻辑分支
    CrashingGit()
    return
  }
  // 被 go test 执行,
  // 设置好环境变量,启动子进程再次执行 TestFailingGit
  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)
}

上面例子的思想是,当Go测试框架运行TestFailingGit时,启动一个子进程(os.Args[0]即生成的Go测试程序)。子进程再次运行测试程序,并只执行TestFailingGit(通过参数 -test.run=TestFailingGit 实现),并且设置了环境变量BE_CRASHING_GIT=1,这样子进程将执行CrashingGit()

技巧六,将mocks、helpers放入testing.go文件中

testing.go文件会被当做一个普通的源码文件,而不是测试代码文件。这样在其它的包中或其它包的测试代码可以使用这些mocks、helpers。

技巧七,单独处理耗时长的测试

当存在一些耗时很长的测试时,等待所有的测试结束会让人烦躁。解决方法是将这些耗时长的测试放入_integration_test.go文件中,并在该文件的头部加入编译tag。看例子:

// +build integration

这样Go测试时默认不会运行这些测试代码。
如果想运行所有的测试代码,你可以这样:

go test -tags=integration

以下是我个人使用alias做的一个简便命令,可以运行当前目录以及子目录中除vendoer目录外的所有测试:

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

这个命令可以配合-v参数使用:

 $ gtest
 …
 $ gtest -v
 …

感谢阅读,英文原文地址:Go advanced testing tips & tricks (https://medium.com/@povilasve/go-advanced-tips-tricks-a872503ac859)

本文作者: yoko
本文链接: http://www.pengrl.com/p/32101/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!

原文地址:https://www.cnblogs.com/notokoy/p/11523496.html

时间: 2024-10-26 01:50:15

[译] Go语言测试进阶版建议与技巧的相关文章

java集合框架小结(进阶版)之HashSet篇

建议先看下:java集合框架小结(进阶版)之HashMap篇 基本概念: hashSet: 根据java集合框架小结(初级版)图示,HashSet是AbstractSet的一个子类,是基于Hash算法的Set接口的实现,顾名思义.允许添加null. --------------------------------------↑ 以上都是扯淡 ↑,↓ HashSet完全是在挂羊头卖狗肉 ↓------------------------------------------- 何谓挂羊头卖狗肉?大家

GO语言的进阶之路-面向对象编程

GO语言的进阶之路-面向对象编程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 当你看完这篇文章之时,我可以说你的Golang算是入门了,何为入门?就是你去看Docker 源码能看懂60%的语法结构,因为涉及一些unix的代码可能没有Linux运维基础的同学在学习的时候会很吃力,看起来也会带来一定的难度,如果有时间的话我会给大家解析Docker部门精辟的源码.好了,回归正题吧,我们今天要学习的内容是什么呢?即面向对象编程.当然,不要用屌丝的心态来说:"那要是没对象的还咋编程呢

逗比之——程序员装逼手册2(进阶版)

1. 着装 一根牛X的程序员是根本没有时间打理自己外貌的,发型就要像爱因斯坦一样,顶着一脑袋鸡窝,凌乱蓬松美,给人随时能从头发里掏出一个鸡蛋的感觉.胡子一大把,彰显自信又从容,不近视则以,近视就要戴酒瓶底子那么厚的大眼镜,一种科研工作者的风格.牛X程序员对自己着装是有高要求的,无论是春夏秋冬,白天晚上,刮风下雨,一个牛X的程序员都要十分在意自己着装,T恤+大花裤衩子+拖鞋是标配,一年365天风雨无阻.换衣服保持一年3-5件T恤的更新频率就可以,T恤大多是参见开源大会免费获得的,上面印着ruby

GoLang入门4-编译应用mymath测试

上面我们已经建立了自己的应用包mymath,如何进行编译安装呢?有两种方式可以进行安装 1.只要进入对应的应用包目录,然后执行go install,就可以安装了 2.在任意的目录执行如下代码go install mymath 安装完之后,我们可以进入如下目录 在任意目录运行 go install mymath  如下,则说明 编译安装包成功! mymath.a 就是编译后的包 这个.a文件是应用包,那么我们如何进行调用呢? 接下来我们新建一个应用程序来调用 GoLang入门4-编译应用mymat

java集合框架小结(进阶版)之HashMap篇

基本概念: Hash(哈希):hash一般也译作“散列”.事实上,就是一个函数,用于直接定址.将数据元素的关键字key作为变量,通过哈希函数,计算生成该元素的存储地址. 冲突:函数是可以多对一的.即:多个自变量可以映射到同一函数值.一般而言,不同的key的hash值是不同的.在往hash表中映射的时候,不同的hash值可能映射到同一存储地址,这种情况被称为冲突. 解决冲突的方法: 1. 链表法:将冲突的各个元素用一个一维数组来维护.(java源码实现) 2. 开发寻址法:具体的有线性探测法.二次

层序遍历及其进阶版

输入为:abd##eh###cfi##j##g## 1.普通层序遍历:输出为一行 2.进阶版1:输出每一层,从左向右依次输出 3.进阶版2:S型输出每一层,即从右向左和从左向右交替输出 #include<iostream> #include<vector> using namespace std; template<class T> struct BiNode { T data; BiNode<T>* leftchild,*rightchild; }; te

ES系统封装教程 高级进阶版 提供Wind7,xp系统下载

 ES系统封装教程 高级进阶版,提供我自己封装的Wind7 x86&x64和XP三个版本的系统下载.这个教程不是为没有基础的人准备的,要想从头学起,我推荐几个基础的教程. 1.使用 VMware Player 创建适合封装的虚拟机 2.使用 Easy Sysprep v4 封装 Windows XP 基础篇 3.使用 Easy Sysprep v4 封装 Windows 7 凡是里面用到的的工具我都会提供下载地址. 虚拟机VM ware10 .系统补丁到2014年10月.系统运行库.封装用的

zip伪加密文件分析(进阶版)

作者近日偶然获得一misc题,本来以为手到擒来,毕竟这是个大家都讨论烂了的题,详情访问链接http://blog.csdn.net/ETF6996/article/details/51946250.既然作为进阶版,当然得对的起这括号里三个字,那道题我断断续续研究了两天,才勉强算是破解了.不废话了,直入主题. 对于普通的zip文件有了解的应该都认得下面这张图片: 对于其中各个字段的含义请大家访问首行提供的链接学习,对于了解过的小伙伴接着往下看,以下是题目中的截图: 仔细点研究会发现这里有五个50

GO语言的进阶之路-go的程序结构以及包简介

GO语言的进阶之路-go的程序结构以及包简介 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.编辑,编译和运行 A,编辑 Go程序使用UTF-8编码的纯Unicode文本编写.大部分现代编译器都能够自动处理编码,并且某些最流行的编辑器还支持Go语言的语法高亮和自动缩进.如果你用的编辑器不支持Go语言,可以在Go语言官网的搜索框中输入编辑器的名字,看看是否有适合的插件可用.为了编辑方便,所有的Go语言关键字和操作符都使用ASCII编码字符,但是Go语言中标识符可以是任一Uni