[譯] Go語言測試進(jìn)階版建議與技巧

閱讀本篇文章前,你最好已經(jīng)知道如何寫基本的單元測試。本篇文章共包含3個小建議,以及7個小技巧。

建議一,不要使用框架

Go語言自身已經(jīng)有一個非常棒的測試框架,它允許你使用Go編寫測試代碼,不需要再額外學(xué)習(xí)其它的庫或測試引擎。關(guān)于斷言方面的幫助函數(shù),你可以看看這個 testing,或者這個 assert.go :)

建議二,使用"_test"包名

相較于直接使用被測試代碼的包名,使用 *_test包名使得測試代碼只能訪問包中對外暴露出的接口。這使得你在寫測試時更多的是站在包使用者的角度來寫,從而使得你可以思考包的接口是否設(shè)計(jì)合理。

建議三,避免全局常量配置項(xiàng)

避免使用全局常量配置項(xiàng),因?yàn)闇y試代碼無法修改常量。下面舉了三個例子做對比:

// 1. 不好,測試代碼無法修改它
const port = 8080

// 2. 好一些,測試代碼可以修改它
var port = 8080

// 3. 更好的方式,測試代碼可以通過 struct 配置 Port
const defaultPort = 8080
type AppConfig {
  Port int // 構(gòu)造函數(shù)中初始化為 defaultPort
}

技巧一,加載測試數(shù)據(jù)

Go對從文件中加載測試數(shù)據(jù)提供了非常好的支持。首先,Go編譯時會忽略testdata目錄。然后,當(dāng)測試代碼運(yùn)行時,Go會將當(dāng)前目錄作為包的目錄。這使得你可以使用相對路徑來訪問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
}

技巧二,保存測試時的預(yù)期結(jié)果至.golden文件中

將測試時的預(yù)期結(jié)果保存至.golden文件中。并且提供一個flag來決定是否更新它。使用這個技巧可以避免在測試代碼中硬編碼預(yù)期輸出結(jié)果非常復(fù)雜的內(nèi)容??蠢樱?/p>

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

技巧三,測試時的初始化、清理代碼

有時候測試代碼比較復(fù)雜,在跑測試的case之前需要初始化好環(huán)境,這可能會包含很多不相關(guān)的錯誤檢查,比如測試文件是否加載成功,測試數(shù)據(jù)是否能按json格式解析等等。這使得測試代碼變得很不純粹優(yōu)雅。

為了解決這個問題,你可以把不相關(guān)的代碼放入幫助函數(shù)中。這些函數(shù)永遠(yuǎn)不返回error,而是傳入*testing.T,當(dāng)有錯誤發(fā)生時直接斷言報(bào)錯。

同樣的,如果幫助函數(shù)需要在結(jié)束后做清理工作,幫助函數(shù)應(yīng)該返回一個函數(shù)做清理工作??蠢樱?/p>

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() { // 返回清理函數(shù),供外部需要清理時調(diào)用
    if err := os.Chdir(old); err != nil {
       t.Fatalf("err: %s", err)
    }
  }
}
func TestThing(t *testing.T) {
  defer testChdir(t, "/other")()
  // ...
}

上面的例子包含了另外一個關(guān)于defer使用的非??岬募记?。defer testChdir(t, "/other")()會先執(zhí)行testChdir內(nèi)的代碼,并且在TestThing結(jié)束時執(zhí)行testChdir所返回的清理函數(shù)中的代碼。

技巧四,當(dāng)依賴第三方可執(zhí)行程序時

有時測試代碼會依賴第三方可執(zhí)行程序,我們可以通過以下方法檢查程序是否存在,存在則執(zhí)行測試,不存在則跳過測試。

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的代碼

該方法通過啟動子進(jìn)程的方式,避免測試包含os.Exit的代碼導(dǎo)致測試程序提前退出。看例子:

func CrashingGit() {
  os.Exit(1)
}
func TestFailingGit(t *testing.T) {
  if os.Getenv("BE_CRASHING_GIT") == "1" { // 子進(jìn)程進(jìn)入這個邏輯分支
    CrashingGit()
    return
  }
  // 被 go test 執(zhí)行,
  // 設(shè)置好環(huán)境變量,啟動子進(jìn)程再次執(zhí)行 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)
}

上面例子的思想是,當(dāng)Go測試框架運(yùn)行TestFailingGit時,啟動一個子進(jìn)程(os.Args[0]即生成的Go測試程序)。子進(jìn)程再次運(yùn)行測試程序,并只執(zhí)行TestFailingGit(通過參數(shù) -test.run=TestFailingGit 實(shí)現(xiàn)),并且設(shè)置了環(huán)境變量BE_CRASHING_GIT=1,這樣子進(jìn)程將執(zhí)行CrashingGit()。

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

testing.go文件會被當(dāng)做一個普通的源碼文件,而不是測試代碼文件。這樣在其它的包中或其它包的測試代碼可以使用這些mocks、helpers。

技巧七,單獨(dú)處理耗時長的測試

當(dāng)存在一些耗時很長的測試時,等待所有的測試結(jié)束會讓人煩躁。解決方法是將這些耗時長的測試放入_integration_test.go文件中,并在該文件的頭部加入編譯tag??蠢樱?/p>

// +build integration

這樣Go測試時默認(rèn)不會運(yùn)行這些測試代碼。
如果想運(yùn)行所有的測試代碼,你可以這樣:

go test -tags=integration

以下是我個人使用alias做的一個簡便命令,可以運(yùn)行當(dāng)前目錄以及子目錄中除vendoer目錄外的所有測試:

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

這個命令可以配合-v參數(shù)使用:

 $ gtest
 …
 $ gtest -v
 …

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容