使用go测试基于urfave/cli的应用程序

使用go测试基于urfave/cli的应用程序,go,testing,command-line-interface,Go,Testing,Command Line Interface,我正在使用urfave/CLI框架在Golang编写一个小型CLI应用程序,我想为它编写测试,但我找不到任何关于如何测试CLI应用程序的有用信息,特别是使用urfave/CLI库编写的。我在应用程序中有很多标志,其中一些是相互排斥的,我希望有一个适当的测试来控制它们——有人知道如何以正确的方式进行吗 编辑: 考虑下面的应用程序的最小的例子,在它们周围有几个标志和限制。您将如何测试这些标志的使用情况(需求、独占性等),以及它们设置与否时如何影响功能 package main import (

我正在使用urfave/CLI框架在Golang编写一个小型CLI应用程序,我想为它编写测试,但我找不到任何关于如何测试CLI应用程序的有用信息,特别是使用urfave/CLI库编写的。我在应用程序中有很多标志,其中一些是相互排斥的,我希望有一个适当的测试来控制它们——有人知道如何以正确的方式进行吗

编辑: 考虑下面的应用程序的最小的例子,在它们周围有几个标志和限制。您将如何测试这些标志的使用情况(需求、独占性等),以及它们设置与否时如何影响功能

package main

import (
    "errors"
    "fmt"
    "os"

    "github.com/urfave/cli"
)

func doSomething(flag1 string, flag2 string, flag3 bool, flag4 bool) error {
    err := errors.New("something")
    return err
}

func main() {
    app := cli.NewApp()
    app.Name = "greet"
    app.Usage = "fight the loneliness!"

    var flag1, flag2 string
    var flag3, flag4 bool

    app.Flags = []cli.Flag{
        cli.StringFlag{
            Name:        "flag1",
            Value:       "",
            Usage:       "flag1",
            Destination: &flag1,
        },
        cli.StringFlag{
            Name:        "flag2",
            Value:       "",
            Usage:       "flag2",
            Destination: &flag2,
        },
        cli.BoolFlag{
            Name:        "flag3",
            Usage:       "flag3",
            Destination: &flag3,
        },
        cli.BoolFlag{
            Name:        "flag4",
            Usage:       "flag4",
            Destination: &flag4,
        },
    }

    app.Action = func(c *cli.Context) error {

        if flag1 != "" && c.NumFlags() > 1 {
            fmt.Println("--flag1 flag cannot be used with any other flags")
            cli.ShowAppHelp(c)
            os.Exit(1)
        }

        if flag1 == "" && flag2 == "" || c.NumFlags() < 1 {
            fmt.Println("--flag2 is required")
            cli.ShowAppHelp(c)
            os.Exit(1)
        }

        if flag3 && flag4 {
            fmt.Println("--flag3 and --flag4 flags are mutually exclusive")
            cli.ShowAppHelp(c)
            os.Exit(1)
        }

        err := doSomething(flag1, flag2, flag3, flag4)
        return err
    }

}
主程序包
进口(
“错误”
“fmt”
“操作系统”
“github.com/urfave/cli”
)
func doSomething(flag1字符串、flag2字符串、flag3布尔、flag4布尔)错误{
err:=errors.New(“某物”)
返回错误
}
func main(){
app:=cli.NewApp()
app.Name=“问候”
app.Usage=“战胜孤独!”
变量flag1,flag2字符串
变量flag3,flag4布尔
app.Flags=[]cli.Flag{
cli.StringFlag{
名称:“flag1”,
值:“”,
用法:“flag1”,
目的地:&flag1,
},
cli.StringFlag{
名称:“flag2”,
值:“”,
用法:“flag2”,
目的地:&flag2,
},
cli.BoolFlag{
名称:“flag3”,
用法:“flag3”,
目的地:&flag3,
},
cli.BoolFlag{
名称:“flag4”,
用法:“flag4”,
目的地:&flag4,
},
}
app.Action=func(c*cli.Context)错误{
如果flag1!=“”&c.NumFlags()>1{
fmt.Println(“--flag1标志不能与任何其他标志一起使用”)
cli.ShowAppHelp(c)
操作系统退出(1)
}
如果flag1==“”&flag2==“”| | c.NumFlags()<1{
fmt.Println(“--flag2是必需的”)
cli.ShowAppHelp(c)
操作系统退出(1)
}
如果标记3和标记4{
fmt.Println(“--flag3和--flag4标志相互排斥”)
cli.ShowAppHelp(c)
操作系统退出(1)
}
错误:=doSomething(flag1、flag2、flag3、flag4)
返回错误
}
}

正如阿德里安正确写道的那样

就像你测试其他东西一样

给出了一个稍微修改过的项目示例代码示例

package main

import (
  "fmt"
  "log"
  "os"

  "github.com/urfave/cli"
)

func Friend(c *cli.Context) error {
  fmt.Println("Hello friend!")
  return nil
}

func main() {
  app := cli.NewApp()
  app.Name = "greet"
  app.Usage = "fight the loneliness!"
  app.Action = Friend

  err := app.Run(os.Args)
  if err != nil {
    log.Fatal(err)
  }
}
因为这段代码实际上是打印一些东西,而不是返回一个可以计算的值,所以可以使用

请注意,
操作
要求。您定义的
ActionFunc
基本上是您自己的事情。它甚至可能来自不同的包装。因此,您的应用程序可测试性的好坏取决于您的设计


编辑动作期望值的签名至少在将来会改变。我已经发现,使用
接口{}
将nil传递给Action,然后检查并键入ActionFunc的assert是有问题的,在这里,no-op ActionFunc实际上也会起到同样的作用,但是删除错误返回值真的让我抓狂。我强烈建议您查看中小型应用程序,或者甚至适用于最复杂的cli应用程序。

通常的答案是“与测试其他任何应用程序的方式相同”。如果您在某些特定方面遇到了问题,您能否澄清您的问题,并在可能的情况下提供您需要测试的代码示例?@Adrian如果是这样,那么为什么没有测试CLI应用程序的真实示例,而只是其中的一些代码(函数)?我特别说过,我在进行标志测试时遇到了麻烦。除了一些GUI实验外,基本上所有的Go程序都是CLI程序,所以一般来说,在Go中进行测试的每个示例都是测试CLI应用程序的示例。你有一些代码可以做一些事情。它需要以可测试的方式进行设计,然后进行测试。如果没有看到您的代码,就没有真正的方法提供更多的建议。如果没有看到您试图测试的代码,就只能提供泛化。您列出的那些程序肯定是CLI程序;它们是从命令行执行的,使用命令行标志和参数。与往常一样,这将增加有人能够帮助您的机会。您尝试了什么?我用稍微修改的代码更新了问题,显示了我关心的标志。您将如何为它们编写测试?@SpankMe再次说明:不要将它们编写为“闭包”,如图所示。使它们成为命名函数,并在“App.Action”中引用该函数。然后,你可以随心所欲地进行测试。对不起,我不明白,你能提供一个如何进行测试的例子吗?@SpankMe我在上面做过。请参见
App.Action=Friend
我仍然不明白,您的示例对标志没有任何作用,而我遇到的问题就是标志测试(应用程序的CLI性质)。
func ExampleFriend(){
   // Yeah, technically, we can save the error check with the code above
   // but this illustrates how you can make sure the output
   // is not what the testable example expects.
   if err := Friend(nil){
     fmt.Printf("Friend: %s",err)
   }

  // Output:
  // Hello friend!
}