Open robert-min opened 1 year ago
os
패키지에서 직접 사용자 입력값을 받고 출력하기 위해 Stdin, Stout 변수를 참조하지 않음 => 변수를 참조할 경우 유닛 테스트 작성에서 매우 불편하기 때문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
}
config
struct : 메모리 내에 어플리케이션의 런타임 동작을 정의하는데 사용
numTimes
field : 출력할 인사 횟수를 결정하는 int 필드printUsage
field : 사용자에게 애플리케이션 동작하는 대신 사용법을 보여주는지 여부를 결정하는 bool 필드strconf.Atoi()
: 숫자의 문자열 -> 해당값의 정수로 변환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
}
numTimes
값이 0보다 작을 경우 오류
func validateArgs(c config) error {
if !(c.numTimes >0) {
return errors.New("Must specify a number greater than 0")
}
return nil
}
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)
}
}
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
}
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)
}
}
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)
}
}
}
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)
}
}
}
runCmd
테스트 함수new(bytes.Buffer)
로 Writer 인터페이스를 구현하는 공백의 Buffer 객체를 생성
package main
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()
}
}
Command line application
기능 정의