【Go】kingpinをinit()でパースしていたらテストでエラーが出た話 #golang
kingpin
標準のflagパッケージでは、同時に長いオプションと短いオプション(shortオプション)を定義するのが面倒。
package main import ( "flag" "fmt" ) var ( dryRun = flag.Bool("dry-run", false, "dry run mode.") ) func init() { flag.BoolVar(dryRun, "n", false, "dry run mode.") flag.Parse() } func main() { fmt.Printf("dry run mode: %v\n", *dryRun) }
説明も二重に出てる。
$ go build -o flag $ ./flag --help Usage of ./flag: -dry-run dry run mode. -n dry run mode.
※説明に関してはパッケージ変数Usage
を上書きすることで変えられる。
上記のような理由でshortオプションの定義がいい感じのパッケージ無いかな〜と探してたらkingpinを見つけた!
kingpinだといい感じにshortオプションを宣言できる!!
package main import ( "fmt" "gopkg.in/alecthomas/kingpin.v2" ) var ( dryRun = kingpin.Flag("dry-run", "dry run mode.").Short('n').Bool() ) func init() { kingpin.Parse() } func main() { fmt.Printf("dry run mode: %v\n", *dryRun) }
Short('n')
だけでshortオプションを定義できる。
説明もデフォルトでいい感じ。
$ go run main.go --help usage: main [<flags>] Flags: --help Show context-sensitive help (also try --help-long and --help-man). -n, --dry-run dry run mode. exit status 1
error in test
便利に使っていたkingpinだけど、テスト時に問題が。。。
フラグを指定していなければ何も問題ない。
$ go test PASS ok gist/kingpin 0.008s
が、フラグを指定するとエラーが。。。
$ go test -v kingpin.test: error: unknown short flag '-t', try --help exit status 1 FAIL gist/kingpin 0.008s
何が起きているのだろうか
-t
の正体を突き止めるために引数を表示するログを入れてみた。
func init() {
fmt.Println(os.Args)
kingpin.Parse()
}
結果
$ go test -v [/tmp/kingpin.test -test.v=true] kingpin.test: error: unknown short flag '-t', try --help exit status 1 FAIL gist/kingpin 0.008s
-test.v=true
←これが原因らしい。
標準のflagパッケージではどうか?
func init() { flag.BoolVar(dryRun, "n", false, "dry run mode.") fmt.Println(os.Args) flag.Parse() }
結果
$ go test -v [/tmp/flag.test -test.v=true] === RUN TestRun --- PASS: TestRun (0.00s) PASS ok gist/flag 0.006s
大丈夫っぽい。
ソースを追ったら、flagパッケージのパッケージ変数CommandLine
をgo test
コマンドとテストバイナリで共有しているから大丈夫だったみたい。
(go test
でテストバイナリ作る時に、go test
用のオプションも含めてバイナリを作っているっぽい。)
確認のため、別なFlagSet
を定義してそちらを使ってみる。
package main import ( "flag" "fmt" "os" ) var ( tflg = flag.NewFlagSet("test_flag", flag.ExitOnError) dryRun = tflg.Bool("dry-run", false, "dry run mode.") ) func init() { tflg.BoolVar(dryRun, "n", false, "dry run mode.") fmt.Println(os.Args) tflg.Parse(os.Args[1:]) } func main() { fmt.Printf("dry run mode: %v\n", *dryRun) result := run(*dryRun) fmt.Printf("result: %v\n", result) } func run(dryRun bool) bool { if dryRun { return false } return true }
FlagSet
はNewFlagSet()
で定義できる。
Parse()
だけ通常と違い、引数を渡してやる必要がある。
結果
$ go test -v [/tmp/flag.test -test.v=true] flag provided but not defined: -test.v Usage of test_flag: -dry-run dry run mode. -n dry run mode. exit status 2 FAIL gist/flag 0.006s
やはり失敗した。
まとめ
- kingpin特有のエラーではなく、flagパッケージでも出る。
- エラーを出さないためには
main()
でパースする必要がある。
基本的な事だけど、init()
はテスト時に呼ばれて、main()
はテスト時に呼ばれないって事だよね。