Cobra로 테스트하기 쉬운 CLI 구성
41910 단어 Goprogrammingtech
참고로 Cobra.Dev는 Cobra에서 CLI를 구성하는 데 사용되는 프레임워크 패키지로 표준Cobra 패키지에 비해 다음과 같은 특징(주요)이 있다.
Go 포장과 결합하여 POSIX/GNU 스타일의 로고를 실현할 수 있습니다.또한 플래그 종속 연결
flag 로고와 설정 파일을 패키지와 결합할 수 있는 내용
이번 제목.
이번에 제작된 CLI 애플리케이션의 사양은 다음과 같습니다.
hash
에 encode
하위 명령encode
은 SHA 256 알고리즘을 통해 입력 텍스트를 해시 값UNIX Philosophy
실제 CLI 앱을 제작하기 전에 디자인 포인터로 쓰이는'UNIX Philosophy'가 있기 때문에 소개해 드리겠습니다.가로되
하위 명령 및 세 번째 모드
하위 명령 방식은 언뜻 보면'UNIX Philosophy'와는 반대인 듯위키백과한 경우 모든 소프트웨어 패키지를 바이너리에 결합하기 위해 관련 기능을 하위 명령으로 편입하는 것이 좋다.
하위 명령을 구성하는 경우 facade pattern을 고려할 수 있습니다.
'파슬'은'건축물의 정면'이라는 뜻으로 시스템 내의 각 서브시스템 창의 역할을 한다.제3자 자신은 서브시스템의 세부 사항을 모르고 상하문 정보만 통해 걷어찬다.비결은 서브시스템 측이father에 의존하지 않고 상하문 정보만 있으면 처리할 수 있다는 것이다.
CLI이기 때문에 서브시스템 측면의 처리 결과는string, []byte, io입니다.Reader 또는 그것들을 출력할 수 있는 형식으로 파삼덕에 되돌려주면 된다.
Cobra 명령을 사용하여 초기 형태 생성
오프닝이 길어졌으니 빨리 Go로 간단한 CLI를 조립하세요.
Go 명령 설치
Cobra 가방에 출력 코드의 초기 도구를 준비했습니다.바이너리에서 제공되지 않았기 때문에
go get
명령으로 구축하고 설치합니다.$ go get -u github.com/spf13/cobra/cobra
main과 cmd 패키지의 생성
먼저 환경을 만들다.이런 느낌 어때요?
$ mkdir hash & cd hash
$ go mod init sample/hash
편의를 위해 가방이나 모듈의 경로를 sample/hash
로 설정합니다.실제로는 github.com/user/hash
와 같은 경로일 것이다.다음은 이전 절에서 만든
cobra
명령main
으로 패키지의 초기 형태를 만듭니다.이런 느낌.$ cobra init --pkg-name sample/hash --viper=false
$ tree .
.
├── LICENSE
├── cmd
│ └── root.go
├── go.mod
└── main.go
이번에는 Cobra의 설정 파일을 제어하지 않기 때문에 --viper=false
옵션을 추가했습니다.LICENSE
파일은 삭제되거나 타당하게 바꿀 수 있다.main.go
의 내용은 이런 느낌(평론 부분 제외)이다.main.go
package main
import "sample/hash/cmd"
func main() {
cmd.Execute()
}
또 cmd/root.go
의 내용은 이런 느낌(리뷰 부분 제외)이다.cmd/root.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"os"
)
var rootCmd = &cobra.Command{
Use: "hash",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func init() {
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
일단 이 상태에서도 작동할 수 있다.$ go run main.go -h
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.
구하고 싶지만 조금만 더 참고 계속 전진하자.하위 명령 추가
하위 명령
encode
을 추가해 보십시오.이런 느낌.추가
$ cobra add encode
$ tree .
.
├── LICENSE
├── cmd
│ ├── encode.go
│ └── root.go
├── go.mod
└── main.go
cmd/encode.go
된 거 아세요?또 기타main.go
또는 cmd/root.go
를 가공하지 않았다.cmd/encode.go
의 내용은 이런 느낌(평론 부분 제외)이다.cmd/encode.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var encodeCmd = &cobra.Command{
Use: "encode",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("encode called")
},
}
func init() {
rootCmd.AddCommand(encodeCmd)
}
이 상태에서 움직이면 이렇게 되는 느낌.$ go run main.go
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.
Usage:
hash [command]
Available Commands:
encode A brief description of your command
help Help about any command
Flags:
-h, --help help for hash
-t, --toggle Help message for toggle
Use "hash [command] --help" for more information about a command.
$ go run main.go encode -h
A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.
Usage:
hash encode [flags]
Flags:
-h, --help help for encode
$ go run main.go encode
encode called
에서 하위 명령encode
을 병합할 수 있다.포맷 코드 다시 쓰기
cobra
명령으로 생성된 모드 코드에 기능을 직접 삽입할 수 있지만 모드 코드에는 다음과 같은 문제가 있다.cmd
패키지 내부에서 표준 입력 출력과 명령행 파라미터를 직접 사용cobra.Command
실례는 cmd
패키지에서 정적 변수로 정의cmd.Execute()
함수 중 일부는 함수os.Exit()
를 통해 강제 종료cmd
포장 자체의 테스트가 어렵기 때문에 테스트에 편리하도록 해 보세요.spiegel-im-spiegel/gocli 패키지 가져오기
직언을 용서하십시오. 저는 여기에 포장Cobra을 도입했습니다.
spf13/viper 봉인은 입력과 출력을 상하문 정보로 전달할 수 있고 이렇게 사용할 수 있다.
package main
import (
"os"
"github.com/spiegel-im-spiegel/gocli/exitcode"
"github.com/spiegel-im-spiegel/gocli/rwi"
)
func run(ui *rwi.RWI) exitcode.ExitCode {
ui.Outputln("Hello world")
return exitcode.Normal
}
func main() {
run(rwi.New(
rwi.WithReader(os.Stdin),
rwi.WithWriter(os.Stdout),
rwi.WithErrorWriter(os.Stderr),
)).Exit()
}
다시 쓰는 cmd
포장의 초기 포장을 사용하세요.하위 명령 재정의
먼저 하위 명령
encode
부터 시작합니다.이런 느낌을 해봤어요.cmd/encode.go
func newEncodeCmd(ui *rwi.RWI) *cobra.Command {
encodeCmd := &cobra.Command{
Use: "encode",
Aliases: []string{"enc", "e"},
Short: "hash input data",
Long: "hash input data (detail)",
RunE: func(cmd *cobra.Command, args []string) error {
if err := ui.Outputln("encode called"); err != nil {
return err
}
return nil
},
}
return encodeCmd
}
따라서 내부 함수를 사용하여 하위 명령encode
에 사용되는 실례를 동적으로 생성할 수 있다.명령 재정의
같은 내용
cobra.Command
도 다시 썼다.cmd/root.go
func newRootCmd(ui *rwi.RWI, args []string) *cobra.Command {
rootCmd := &cobra.Command{
Use: "hash",
Short: "Hash functions",
Long: "Hash functions (detail)",
}
rootCmd.SilenceUsage = true
rootCmd.SetArgs(args) //arguments of command-line
rootCmd.SetIn(ui.Reader()) //Stdin
rootCmd.SetOut(ui.ErrorWriter()) //Stdout -> Stderr
rootCmd.SetErr(ui.ErrorWriter()) //Stderr
rootCmd.AddCommand(
newEncodeCmd(ui),
)
return rootCmd
}
func Execute(ui *rwi.RWI, args []string) exitcode.ExitCode {
if err := newRootCmd(ui, args).Execute(); err != nil {
return exitcode.Abnormal
}
return exitcode.Normal
}
겸사겸사 root.go
함수의rootCmd.SetArgs(args) //arguments of command-line
rootCmd.SetIn(ui.Reader()) //Stdin
rootCmd.SetOut(ui.ErrorWriter()) //Stdout -> Stderr
rootCmd.SetErr(ui.ErrorWriter()) //Stderr
섹션에서 명령행 매개변수와 입력 출력을 설정합니다.또한 newRootCmd()
함수 내부에서는 반환값을 평가하는 error 실례가 없어 보이지만 실제로cmd.Execute()
는 방법 내부에서 잘못된 내용을 평가했기 때문에cobra.Command.Execute()
함수는 cmd.Execute()
의 진위만 보인다.마지막
err != nil
함수도 상술한 내용에 따라 수정해야 한다.main.go
func main() {
cmd.Execute(
rwi.New(
rwi.WithReader(os.Stdin),
rwi.WithWriter(os.Stdout),
rwi.WithErrorWriter(os.Stderr),
),
os.Args[1:],
).Exit()
}
이 정도면 됐어.테스트 명령
쓴 코드를 써야 하는 테스트입니다.이런 느낌?
cmd/encode_test.go
func TestEncode(t *testing.T) {
testCases := []struct {
inp string
outp string
ext exitcode.ExitCode
}{
{inp: "hello world\n", outp: "a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447\n", ext: exitcode.Normal},
}
for _, tc := range testCases {
r := strings.NewReader(tc.inp)
wbuf := &bytes.Buffer{}
ebuf := &bytes.Buffer{}
ext := Execute(
rwi.New(
rwi.WithReader(r),
rwi.WithWriter(wbuf),
rwi.WithErrorWriter(ebuf),
),
[]string{"encode"},
)
if ext != tc.ext {
t.Errorf("Execute() is \"%v\", want \"%v\".", ext, tc.ext)
fmt.Println(ebuf.String())
}
str := wbuf.String()
if str != tc.outp {
t.Errorf("Execute() -> \"%v\", want \"%v\".", str, tc.outp)
}
}
}
main()
함수의 매개 변수를 주의하세요.실행 결과$ go test ./...
? sample/hash [no test files]
--- FAIL: TestEncode (0.00s)
encode_test.go:36: Execute() -> "encode called
", want "a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
".
FAIL
FAIL sample/hash/cmd 0.002s
FAIL
덮어쓰기.중요한 거 까먹었어 (웃음) 그럼 Execute()
서류 하나 만들어encode/encode.go
package encode
import (
"crypto"
"errors"
"io"
)
var (
ErrNoImplement = errors.New("no implementation")
)
//Value returns hash value string from io.Reader
func Value(r io.Reader, alg crypto.Hash) ([]byte, error) {
if !alg.Available() {
return nil, ErrNoImplement
}
h := alg.New()
if _, err := io.Copy(h, r); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
이런 느낌 괜찮아요?encode/encode.go
함수에 결합cmd/encode.go
func newEncodeCmd(ui *rwi.RWI) *cobra.Command {
encodeCmd := &cobra.Command{
Use: "encode",
Aliases: []string{"enc", "e"},
Short: "hash input data",
Long: "hash input data (detail)",
RunE: func(cmd *cobra.Command, args []string) error {
v, err := encode.Value(ui.Reader(), crypto.SHA256)
if err != nil {
return err
}
fmt.Fprintf(ui.Writer(), "%x\n", v)
return nil
},
}
return encodeCmd
}
테스트를 다시 시작하다.좋아, 응, 좋아.
마지막으로 명령을 실제로 집행하다.
$ go test ./...
? sample/hash [no test files]
ok sample/hash/cmd 0.003s
? sample/hash/encode [no test files]
순조롭게 집행하다.또 이번에 쓴 코드는 spiegel-im-spiegel/gocli 위에 놓았다.참고해주세요.
Reference
이 문제에 관하여(Cobra로 테스트하기 쉬운 CLI 구성), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/spiegel/articles/20201018-cli-with-cobra-and-golang텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)