Go 重構筆記 2 - Get test data path

Background

測試的時候會偶爾需要測試資料來 mock 真實資料,通常會在根目錄建一個 testdata 的 folder, 然後取得的時候這樣寫:

my-project/internal/foo_test.go

func TestFoo(t *testing.T) {
    testfile := "../testdata/testfile"
    // do some stuff
}

my-project/internal/one/two/bar_test.go

func TestBar(t *testing.T) {
    testfile := "../../../testdata/testfile"
    // do some stuff
}

可以發現當相對位置不一樣的時候,就要去改變目錄的路徑,相當惱人。

runtime.Caller

func Caller(skip int) (pc uintptr, file string, line int, ok bool)

runtime.Caller 可以顯示執行過程中呼叫的訊息,藉由 skip 參數的不同,還可以往前 trace 呼叫的順序。

skip=0 表示 caller 自己

package main

import (
    "fmt"
    "runtime"
)

func main() {
    _, file, line, _ := runtime.Caller(0)
    fmt.Println(file, line)
    // /Users/clouding/go/src/github.com/cloudingcity/testing/main.go 9
}

上面的例子可以得知 runtime.Caller 是在 main.go#9 執行的。

Solution

根據 runtime.Caller,可以知道 runtime 中該 file 的完整路徑,當然也可以得到他的目錄路徑。

在 testdata 目錄中新增一個 file my-project/testdata/testdata.go

package testdata

import (
    "path/filepath"
    "runtime"
)

var basepath string

func init() {
    _, currentFile, _, _ := runtime.Caller(0)
    basepath = filepath.Dir(currentFile)
}

func Path(rel string) string {
    return filepath.Join(basepath, rel)
}

filepath.Dir 取得 testdata 的目錄路徑,Path function 給相對路徑然後返回完整路徑。

Refactor

my-project/internal/foo_test.go

func TestFoo(t *testing.T) {
    testfile := testdata.Path("testfile")
    // do some stuff
}

my-project/internal/one/two/bar_test.go

func TestBar(t *testing.T) {
    testfile := testdata.Path("testfile")
    // do some stuff
}

重構之後就乾淨很多,之後要取得 testdata 目錄下的檔案也只要使用 testdata.Path() 就可以了。

Read More