Go test 传递命令行参数及解析所遇到的错误及解决

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 命令格式不能改变,多一个参数或者少一个参数,都可能使切片出错,导致传递的参数不符合要求;

再仔细想一想,函数 ProcessArgsos.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

猜你喜欢

转载自blog.csdn.net/Lance_of_Longinus/article/details/108832708
今日推荐