英文:
Passing Persistant flags for Cobra CLI for testing
问题
我有一个使用Cobra编写的CLI应用程序。该应用程序包含一个名为RootCmd的根命令,其中包含两个PersistentFlags。其中一个持久标志称为threads,默认值为1,简写为-h。
在PersistentPreRun中,我检查该值是否小于1,如果是,则打印错误消息threads不能小于1并退出。
问题是,我需要为应用程序编写一些测试,但我找不到设置该标志的方法。目前,我使用os/exec进行测试,这非常麻烦,并且无法提供代码覆盖率。
我正在尝试以下操作:
func Test(t *testing.T) {
    root := cmd.RootCmd
    root.SetArgs([]string{"-t", "12"})
    // 或者:root.SetArgs([]string{"-t 12"})
    root.Execute()
}
这会输出消息Error: unknown shorthand flag: 't' in -t 12。如果完全省略该标志并尝试使用任何子命令,则会显示错误消息,指出该值不能小于1(请注意,我设置了默认值)。
是否有除了SetArgs之外的设置标志的方法或解决方法?
英文:
I have a CLI application written in Cobra. The app contains a root command RootCmd which contains two PersistentFlags. One of the persistent flags is called threads which has a default value of 1 and shorthand -h.
	RootCmd.PersistentFlags().IntVarP(&threads, "threads", "t", 1, "Number of concurrent workers, using Stdin overrides this flag")
In the PersistentPreRun I have a check if the value is set to less than 1 to print the error message threads can't be less than 1 and exit.
The problem is I need to write some tests for the application and I can't find a way to set the flag. Currently, I test with os/exec, which is extremely annoying and doesn't provide code coverage.
I'm trying to do the following
func Test(t *testing.T) {
	root := cmd.RootCmd
	root.SetArgs([]string{"-t", "12"})
    // or even: root.SetArgs([]string{"-t 12"})
	root.Execute()
}
This outputs the message Error: unknown shorthand flag: 't' in -t 12. Omitting the flag entirely and trying to use any subcommand shows the error message that the value can't be less than one (mind you I set a default value).
Is there a way to set flags other than SetArgs or a workaround?
答案1
得分: 4
我无法重现这个问题。
我通过运行cobra-cli init创建了一个简单的Cobra应用程序,并添加了一个名为foo的子命令。这给我提供了以下布局:
.
├── cmd
│   ├── foo.go
│   └── root.go
├── go.mod
├── go.sum
└── main.go
main.go非常简单:
package main
import "clitest/cmd"
func main() {
  cmd.Execute()
}
在cmd/root.go中,我添加了一个名为--threads(或-t)的持久标志:
package cmd
import (
  "fmt"
  "os"
  "github.com/spf13/cobra"
)
var threads int
var rootCmd = &cobra.Command{
  Use:   "clitest",
  Short: "A simple cli test",
  RunE:  runRoot,
}
func runRoot(cmd *cobra.Command, args []string) error {
  fmt.Printf("This is the root command, threads=%d\n", threads)
  return nil
}
func Execute() {
  err := rootCmd.Execute()
  if err != nil {
    os.Exit(1)
  }
}
func init() {
  rootCmd.PersistentFlags().IntVarP(&threads, "threads", "t", 1, "Number of threads")
}
在cmd/foo.go中,我定义了一个名为foo的子命令:
package cmd
import (
  "fmt"
  "github.com/spf13/cobra"
)
var count int
var fooCmd = &cobra.Command{
  Use:   "foo",
  Short: "The foo command",
  RunE:  runFoo,
}
func runFoo(cmd *cobra.Command, args []string) (err error) {
  fmt.Printf("This is the foo command; count=%d\n", count)
  return nil
}
func init() {
  fooCmd.Flags().IntVarP(&count, "count", "c", 0, "Count of foo")
  rootCmd.AddCommand(fooCmd)
}
有了上述代码,我可以运行以下命令:
$ ./clitest
This is the root command, threads=1
$ ./clitest -t 12
This is the root command, threads=12
$ ./clitest foo
This is the foo command; count=0, threads=1
$ ./clitest foo -t 12 -c 2
This is the foo command; count=2, threads=12
我可以这样为根命令编写一个测试:
package cmd
import (
	"testing"
)
func TestRootCmdWithArgs(t *testing.T) {
	rootCmd.SetArgs([]string{"-t", "12"})
	if err := rootCmd.Execute(); err != nil {
		t.Errorf("failed to execute rootCmd")
	}
	if threads != 12 {
		t.Errorf("expected 12, got %d", threads)
	}
}
func TestRootCmdInvalidArgs(t *testing.T) {
	rootCmd.SetArgs([]string{"--arg-that-does-not-exist"})
	if err := rootCmd.Execute(); err == nil {
		t.Errorf("command succeeded when it should have failed")
	}
}
func TestFooCmdWithArgs(t *testing.T) {
	rootCmd.SetArgs([]string{"foo", "-c", "2"})
	if err := rootCmd.Execute(); err != nil {
		t.Errorf("failed to execute rootCmd")
	}
	if count != 2 {
		t.Errorf("expected 2, got %d", count)
	}
}
这些测试按预期成功运行:
$ go test ./...
?   	clitest	[no test files]
ok  	clitest/cmd	(cached)
你可以在这个存储库中找到本答案中提到的所有文件。
英文:
I can't reproduce this problem.
I have a simple Cobra application I've created by running cobra-cli init, and I've added a single subcommand, foo. That gives me this layout:
.
├── cmd
│   ├── foo.go
│   └── root.go
├── go.mod
├── go.sum
└── main.go
main.go is minimal:
package main
import "clitest/cmd"
func main() {
  cmd.Execute()
}
In cmd/root.go I add a single PersistentFlag, --threads (or -t):
package cmd
import (
  "fmt"
  "os"
  "github.com/spf13/cobra"
)
var threads int
var rootCmd = &cobra.Command{
  Use:   "clitest",
  Short: "A simple cli test",
  RunE:  runRoot,
}
func runRoot(cmd *cobra.Command, args []string) error {
  fmt.Printf("This is the root command, threads=%d\n", threads)
  return nil
}
func Execute() {
  err := rootCmd.Execute()
  if err != nil {
    os.Exit(1)
  }
}
func init() {
  rootCmd.PersistentFlags().IntVarP(&threads, "threads", "t", 1, "Number of threads")
}
In cmd/foo.go I define a single subcommand:
package cmd
import (
  "fmt"
  "github.com/spf13/cobra"
)
var count int
var fooCmd = &cobra.Command{
  Use:   "foo",
  Short: "The foo command",
  RunE:  runFoo,
}
func runFoo(cmd *cobra.Command, args []string) (err error) {
  fmt.Printf("This is the foo command; count=%d\n", count)
  return nil
}
func init() {
  fooCmd.Flags().IntVarP(&count, "count", "c", 0, "Count of foo")
  rootCmd.AddCommand(fooCmd)
}
With the above code in place, I can run:
$ ./clitest
This is the root command, threads=1
$ ./clitest -t 12
This is the root command, threads=12
$ ./clitest foo
This is the foo command; count=0, threads=1
$ ./clitest foo -t 12 -c 2
This is the foo command; count=2, threads=12
I can write a test for the root command like this:
package cmd
import (
	"testing"
)
func TestRootCmdWithArgs(t *testing.T) {
	rootCmd.SetArgs([]string{"-t", "12"})
	if err := rootCmd.Execute(); err != nil {
		t.Errorf("failed to execute rootCmd")
	}
	if threads != 12 {
		t.Errorf("expected 12, got %d", threads)
	}
}
func TestRootCmdInvalidArgs(t *testing.T) {
	rootCmd.SetArgs([]string{"--arg-that-does-not-exist"})
	if err := rootCmd.Execute(); err == nil {
		t.Errorf("command succeeded when it should have failed")
	}
}
func TestFooCmdWithArgs(t *testing.T) {
	rootCmd.SetArgs([]string{"foo", "-c", "2"})
	if err := rootCmd.Execute(); err != nil {
		t.Errorf("failed to execute rootCmd")
	}
	if count != 2 {
		t.Errorf("execpted 2, got %d", count)
	}
}
These tests succeed as expected:
$ go test ./...
?   	clitest	[no test files]
ok  	clitest/cmd	(cached)
You can find all the files referenced in this answer in this repository.
答案2
得分: 0
我认为我找到了问题所在。感谢Iarsks提供的示例。
我之前的代码是这样的:
func Execute() {
    RootCmd.CompletionOptions.HiddenDefaultCmd = true
    RootCmd.PersistentFlags().IntVarP(&threads, "threads", "t", 1, "Number of concurrent workers, using Stdin overrides this flag")
    RootCmd.PersistentFlags().StringVarP(&delimiter, "delimiter", "d", ",", "Choose delimiter")
    if err := RootCmd.Execute(); err != nil {
        fmt.Fprintf(os.Stderr, "csvutil encountered an error while executing")
        os.Exit(1)
    }
}
我将这个函数拆分成了两部分:
func init() {
    RootCmd.CompletionOptions.HiddenDefaultCmd = true
    RootCmd.PersistentFlags().IntVarP(&threads, "threads", "t", 1, "Number of concurrent workers, using Stdin overrides this flag")
    RootCmd.PersistentFlags().StringVarP(&delimiter, "delimiter", "d", ",", "Choose delimiter")
}
func Execute() {
    if err := RootCmd.Execute(); err != nil {
        fmt.Fprintf(os.Stderr, "csvutil encountered an error while executing")
        os.Exit(1)
    }
}
现在它正常工作了。
英文:
I think I found the problem. Thanks to the example provided by Iarsks.
My previous root had
func Execute() {
	RootCmd.CompletionOptions.HiddenDefaultCmd = true
	RootCmd.PersistentFlags().IntVarP(&threads, "threads", "t", 1, "Number of concurrent workers, using Stdin overrides this flag")
	RootCmd.PersistentFlags().StringVarP(&delimiter, "delimiter", "d", ",", "Choose delimiter")
	if err := RootCmd.Execute(); err != nil {
		fmt.Fprintf(os.Stderr, "csvutil encountered an error while executing")
		os.Exit(1)
	}
}
I split this function into:
func init() {
	RootCmd.CompletionOptions.HiddenDefaultCmd = true
	RootCmd.PersistentFlags().IntVarP(&threads, "threads", "t", 1, "Number of concurrent workers, using Stdin overrides this flag")
	RootCmd.PersistentFlags().StringVarP(&delimiter, "delimiter", "d", ",", "Choose delimiter")
}
func Execute() {
	if err := RootCmd.Execute(); err != nil {
		fmt.Fprintf(os.Stderr, "csvutil encountered an error while executing")
		os.Exit(1)
	}
}
And now it works fine.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论