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

I'm writing a small CLI application in Golang using urfave/cli framework and I'd like to write tests for it, but I can't find any useful information on how to test CLI applications, specifically written with the urfave/cli library. I have a lot of flags in the application and some of them are mutually exclusive and I'd like a proper test to stay on top of them - does anyone have an idea how to do it the right way?

EDIT: Consider the following minimal example of application with several flags and restrictions around them. How would you test these flags usage (requirements, exclusivity, etc.) and how they influence the functions when they're set or not?

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
    }

}

As Adrian correctly wrote

the same way you test anything else

Given a slightly modified example of the sample code of the project

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)
  }
}

Since this code actually prints something instead of returning a value you can evaluate, you could use a testable example

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!
}

Note that Action expects an ActionFunc. Where you define that ActionFunc is pretty much your thing. It could even come from a different package. So it is your design on how good your application will be testable.

Edit The signature of the value Action expects will change in the future, at least according to the docs. I already find it questionable to use interface{} to be able to pass nil to Action, and then check and type assert for ActionFunc, where a no-op ActionFunc would actually serve the same purpose, but removing an error return value really makes me scratch my head. I strongly recommend to have a look at alecthomas/kingpin for smaller to medium size applications or spf13/cobra, which is suitable even for the most complex of cli applications.