這周的服務計算作業是使用 Golang 開發 selpg 命令列程式,selpg 程式詳見 開發 Linux 命令列實用程式,筆者對其的 Golang 實現詳見 gitee ;
在好不容易實現了 selpg 所需要的函數後,當然是要對其進行測試,但是在編寫測試函數過程中,遇到了許多問題,經過一番摸索嘗試後,才將其解決,在此將方法記錄下來,和大家分享;
以下將結合 selpg 中函數 ProcessArgs
來說明;selpg 中函數 ProcessArgs
負責檢查命令列引數合法性並解析,大概流程是先通過 os.Args
來檢查必選引數 -s
和 -e
是否存在,再用 pflag.XxxVarP
繫結引數到相應變數,使用 pflag.Parse
解析;
一般情況下,我們是直接在終端輸入諸如 selpg -s1 -e1 input.txt
命令來呼叫 selpg ,但現在我們要在 go test 中傳遞 selpg -s1 -e1 input.txt
,在經過查閱 相關部落格 ,發現可以使用 go test
的 -args
標籤,該標籤會把其後的所有字串當做引數傳入,所以我嘗試使用如下命令進行測試;
go test -v -args selpg -s1 -e1 input.txt
func TestProcessArgs(t *testing.T){
var args selpgArgs
expectedArgs := selpgArgs{1, 1, "input.txt", 10, false, ""}
ProcessArgs(&args)
if args != expectedArgs {
t.Errorf("got %v, expected %v", args, expectedArgs)
}
}
結果毫無疑問地出錯了,因為使用 go test -v -args selpg -s1 -e1 input.txt
命令,得到的 os.Args
會是這樣的(「…」代表前面的路徑資訊);
.../selpg.test -test.timeout=10m0s -test.v=true selpg -s1 -e1 input.txt
我們想要的引數位於 os.Args
最後,而函數 ProcessArgs
是從 os.Args
前面開始檢查必選引數 -s
和 -e
是否存在,難道要修改原始碼中的函數 ProcessArgs
來適應測試?好像並不是很合適;
那麼如何才能拿到我們想要的引數呢?很簡單,既然需要的引數被放在了最後,那就把前面不需要的引數都去掉不就好了;
os.Args
本質上是一個 []string
陣列,,os
包被匯入時對其進行建立並賦予命令列引數值,那麼我們可以對其進行切片操作,將前面不需要的都去掉即可;所以做出如下修改
func TestProcessArgs(t *testing.T){
var args selpgArgs
expectedArgs := selpgArgs{1, 1, "input.txt", 10, false, ""}
os.Args = os.Args[3:len(os.Args)]
ProcessArgs(&args)
if args != expectedArgs {
t.Errorf("got %v, expected %v", args, expectedArgs)
}
}
這樣,測試就能順利執行,函數 ProcessArgs
能拿到需要的命令列引數並解析,但是這樣做的不足之處在於, go test
命令格式不能改變,多一個引數或者少一個引數,都可能使切片出錯,導致傳遞的引數不符合要求;
再仔細想一想,函數 ProcessArgs
從 os.Args
裡拿引數,測試中 os.Args
格式不符合要求,對 os.Args
切片又不夠靈活;既然 os.Args
本質上是一個 []string
陣列,那直接寫一個新的 []string
陣列,賦給 os.Args
不就好了;所以做出如下修改
func TestProcessArgs(t *testing.T){
var args selpgArgs
cmd := []string{"selpg", "-s1", "-e1", "input.txt"}
expectedArgs := selpgArgs{1, 1, "input.txt", 10, false, ""}
os.Args = cmd
ProcessArgs(&args)
if args != expectedArgs {
t.Errorf("got %v, expected %v", args, expectedArgs)
}
}
這樣,只使用 go test
,測試也能夠順利執行,不需要在使用 -args
標籤傳遞引數
以上只實現了一個測例的測試函數,如果想增加多個測例,只需要簡單的修改即可
func TestProcessArgs(t *testing.T) {
cases := []struct {
cmd []string
expectedArgs selpgArgs
}{
{
[]string{"selpg", "-s1", "-e1"},
selpgArgs{1, 1, "", 10, false, ""},
},
{
[]string{"selpg", "-s1", "-e1", "input.txt"},
selpgArgs{1, 1, "input.txt", 10, false, ""},
},
}
for i, c := range cases {
var args selpgArgs
os.Args = c.cmd
ProcessArgs(&args)
if args != c.expectedArgs {
t.Errorf("case %v: got %v, expected %v", i, args, c.expectedArgs)
}
}
}
但是,如果 go test
測試的話,會出現 flag redefined
引數解析錯誤
.../selpg.test flag redefined: ...
這個問題的解決思路來自 Golang : flag 包簡介 ,總的來說,flag
包被匯入時建立了 FlagSet
型別的全域性物件 CommandLine
,在程式中定義的所有命令列引數變數都會被加入到 CommandLine
的 formal 屬性中
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
所以在每個測例中,除了要更新 os.Args
,還需要更新 flag.CommandLine
,所以做出如下修改
func TestProcessArgs(t *testing.T) {
cases := []struct {
cmd []string
expectedArgs selpgArgs
}{
{
[]string{"selpg", "-s1", "-e1"},
selpgArgs{1, 1, "", 10, false, ""},
},
{
[]string{"selpg", "-s1", "-e1", "input.txt"},
selpgArgs{1, 1, "input.txt", 10, false, ""},
},
}
for i, c := range cases {
var args selpgArgs
os.Args = c.cmd
pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
ProcessArgs(&args)
if args != c.expectedArgs {
t.Errorf("case %v: got %v, expected %v", i, args, c.expectedArgs)
}
}
}
[]string
陣列,再賦給 os.Args
即可;flag
包解析引數時,還需要用 flag.CommandLine = NewFlagSet(os.Args[0], ExitOnError)
更新 flag.CommandLine