Go test 传递命令行参数及解析所遇到的错误及解决
概述
这周的服务计算作业是使用 Golang 开发 selpg 命令行程序,selpg 程序详见 开发 Linux 命令行实用程序,笔者对其的 Golang 实现详见 gitee ;
在好不容易实现了 selpg 所需要的函数后,当然是要对其进行测试,但是在编写测试函数过程中,遇到了许多问题,经过一番摸索尝试后,才将其解决,在此将方法记录下来,和大家分享;
以下将结合 selpg 中函数 ProcessArgs
来说明;selpg 中函数 ProcessArgs
负责检查命令行参数合法性并解析,大概流程是先通过 os.Args
来检查必选参数 -s
和 -e
是否存在,再用 pflag.XxxVarP
绑定参数到相应变量,使用 pflag.Parse
解析;
Go test 传递命令行参数
一般情况下,我们是直接在终端输入诸如 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
标签传递参数
Go test 参数解析
以上只实现了一个测例的测试函数,如果想增加多个测例,只需要简单的修改即可
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)
}
}
}
总结
- 在 Go test 传递命令行参数,可以将参数在测试函数中写成
[]string
数组,再赋给os.Args
即可; - 如果还使用了
flag
包解析参数时,还需要用flag.CommandLine = NewFlagSet(os.Args[0], ExitOnError)
更新flag.CommandLine