robert-min / handson-go

Go-lang hands on guide
0 stars 0 forks source link

Chapter1. Command line application #1

Open robert-min opened 1 year ago

robert-min commented 1 year ago

Command line application

기능 정의

robert-min commented 1 year ago

사용자 이름을 입력받는 함수

func getName(r io.Reader, w io.Writer) (string, error) {
    msg := "Your name? Press the Enter key when done \n"
    fmt.Fprintf(w, msg)
    scanner := bufio.NewScanner(r)
    scanner.Scan()
    if err := scanner.Err(); err != nil {
        return "", err
    }
    name := scanner.Text()
    if len(name) == 0{
        return "", errors.New("You didn't enter your name")
    }
    return name, nil
}
robert-min commented 1 year ago

입력값을 파싱하는 함수

type config struct {
    numTimes   int
    printUsage bool
}

func parseArgs(args []string) (config, error) {
    var numTimes int
    var err error
    c := config{}
    if len(args) != 1 {
        return c, errors.New("Invalid number of arguments")
    }

    if args[0] == "-h" || args[0] == "--help" {
        c.printUsage = true
        return c, nil
    }

    numTimes, err = strconv.Atoi(args[0])
    if err != nil {
        return c, err
    }
    c.numTimes = numTimes

    return c, nil
}
robert-min commented 1 year ago

입력값 유효성 검사 함수

robert-min commented 1 year ago

설정값에 따라 사용자 화면에 인사 출력하는 함수

func greetUser(c config, name string, w io.Writer) {
    msg := fmt.Sprintf("Nice to meet you %s\n", name)
    for i := 0; i < c.numTimes; i++ {
        fmt.Fprintf(w, msg)
    }
}

config에 포함된 값에 따라 동작을 수행하는 함수

func runCmd(r io.Reader, w io.Writer, c config) error {
    if c.printUsage {
        printUsage(w)
        return nil
    }
    name, err := getName(r, w)
    if err != nil {
        return err
    }
    greetUser(c, name, w)
    return nil
}
robert-min commented 1 year ago

main 함수

func main() {
    c, err := parseArgs(os.Args[1:])
    if err != nil {
        fmt.Fprintln(os.Stdout, err)
        printUsage(os.Stdout)
        os.Exit(1)
    }
    err = validateArgs(c)
    if err != nil {
        fmt.Fprintln(os.Stdout, err)
        printUsage(os.Stdout)
        os.Exit(1)
    }
    err = runCmd(os.Stdin, os.Stdout, c)
    if err != nil {
        fmt.Fprintln(os.Stdout, err)
        os.Exit(1)
    }
}
robert-min commented 1 year ago

parseArgs 테스트 함수

package main

import (
    "errors"
    "testing"
)

func TestParseArgs(t *testing.T) {
    type testConfig struct {
        args []string
        err  error
        config
    }

    tests := []testConfig{
        {
            args:   []string{"-h"},
            err:    nil,
            config: config{printUsage: true, numTimes: 0},
        },
        {
            args:   []string{"10"},
            err:    nil,
            config: config{printUsage: false, numTimes: 10},
        },
        {
            args:   []string{"abc"},
            err:    errors.New("strconv.Atoi: parsing \"abc\": invalid syntax"),
            config: config{printUsage: false, numTimes: 0},
        },
        {
            args:   []string{"1", "foo"},
            err:    errors.New("Invalid number of arguments"),
            config: config{printUsage: false, numTimes: 0},
        },
    }

    for _, tc := range tests {
        c, err := parseArgs(tc.args)
        if tc.err != nil && err.Error() != tc.err.Error() {
            t.Fatalf("Expected error to be: %v, got: %v\n", tc.err, err)
        }
        if tc.err == nil && err != nil {
            t.Fatalf("Expected nil error, got: %v\n", err)
        }
        if c.printUsage != tc.printUsage {
            t.Errorf("Expected printUsage to be: %v, got: %v\n", tc.printUsage, c.printUsage)
        }
        if c.numTimes != tc.numTimes {
            t.Errorf("Expected numTimes to be: %v, got: %v\n", tc.numTimes, c.numTimes)
        }
    }
}
robert-min commented 1 year ago

validateArgs 테스트 함수

package main

import (
    "errors"
    "testing"
)

func TestValidateArgs(t *testing.T) {
    tests := []struct {
        c   config
        err error
    }{
        {
            c:   config{},
            err: errors.New("Must specify a number greater than 0"),
        },
        {
            c:   config{numTimes: -1},
            err: errors.New("Must specify a number greater than 0"),
        },
        {
            c:   config{numTimes: 10},
            err: nil,
        },
    }

    for _, tc := range tests {
        err := validateArgs(tc.c)
        if tc.err != nil && err.Error() != tc.err.Error() {
            t.Errorf("Expected error to be: %v, got: %v\n", tc.err, err)
        }
        if tc.err == nil && err != nil {
            t.Errorf("Expected nil error, got: %v\n", err)
        }
    }
}
robert-min commented 1 year ago

runCmd 테스트 함수

import ( "bytes" "errors" "strings" "testing" )

func TestRunCmd(t *testing.T) { tests := []struct { c config input string output string err error }{ { c: config{printUsage: true}, output: usageString, }, { c: config{numTimes: 5}, input: "", output: strings.Repeat("Your name please? Press the Enter key when done.\n", 1), err: errors.New("You didn't enter your name"), }, { c: config{numTimes: 5}, input: "Bill Bryson", output: "Your name please? Press the Enter key when done.\n" + strings.Repeat("Nice to meet you Bill Bryson\n", 5), }, } byteBuf := new(bytes.Buffer) for _, tc := range tests { rd := strings.NewReader(tc.input) err := runCmd(rd, byteBuf, tc.c) if err != nil && tc.err == nil { t.Fatalf("Expected nil error, got: %v\n", err) } if tc.err != nil { if err.Error() != tc.err.Error() { t.Fatalf("Expected error: %v, Got error: %v\n", tc.err.Error(), err.Error()) } } gotMsg := byteBuf.String() if gotMsg != tc.output { t.Errorf("Expected stdout message to be: %v, Got: %v\n", tc.output, gotMsg) }

    byteBuf.Reset()
}

}