Testing CLI Tools with Go
11 January 2018
Ricardo Gerardi
Ricardo Gerardi
package main import "fmt" func main() { fmt.Println("Type the first number:") n1 := 0.0 fmt.Scanf("%f\n", &n1) fmt.Println("Type the second number:") n2 := 0.0 fmt.Scanf("%f\n", &n2) fmt.Println("The sum is:", n1+n2) }
main.go
func main() { fmt.Println("Type the first number:") n1 := 0.0 fmt.Scanf("%f\n", &n1) fmt.Println("Type the second number:") n2 := 0.0 fmt.Scanf("%f\n", &n2) fmt.Println("The sum is:", add(n1, n2)) } func add(n1, n2 float64) float64 { return n1 + n2 }
main test.go
package main import ( "testing" ) func Test_Add(t *testing.T) { sum := add(3.2, 6.8) if sum != 10.0 { t.Errorf("Sum should be 10. Got %f.", sum) } }
E.g: Input data
func main() { n1 := askNumber(os.Stdin) n2 := askNumber(os.Stdin) sum := add(n1, n2) printSum(os.Stdout, sum) } func askNumber(in io.Reader) float64 { fmt.Println("Type a number:") n := 0.0 fmt.Fscanf(in, "%f\n", &n) return n }
func Test_askNumber(t *testing.T) { in := bytes.NewBufferString("5") var exp float64 = 5 res := askNumber(in) if exp != res { t.Errorf("Expected %f, got %f\n", exp, res) } }
E.g: or Output...
func printSum(out io.Writer, sum float64) { fmt.Fprintln(out, "The sum is:", sum) }
func Test_PrintSum(t *testing.T) { out := bytes.NewBuffer([]byte{}) var sum float64 = 5 exp := fmt.Sprintln("The sum is:", sum) printSum(out, sum) if exp != out.String() { t.Errorf("Expected '%s', got '%s'\n", exp, out.String()) } }
$ go test -coverprofile=coverage.out $ go tool cover -html=coverage.out
func main() { os.Exit(run(os.Stdin, os.Stdout)) } func run(in io.Reader, out io.Writer) int { n1 := askNumber(in) n2 := askNumber(in) sum := add(n1, n2) printSum(out, sum) return 0 }
func Test_Run(t *testing.T) { in := bytes.NewBufferString("5\n5\n") out := bytes.NewBuffer([]byte{}) expRetCode := 0 exp := fmt.Sprintln("The sum is:", 10) retCode := run(in, out) if expRetCode != retCode { t.Errorf("Expected %d, got %d\n", expRetCode, retCode) } if exp != out.String() { t.Errorf("Expected '%s', got '%s'\n", exp, out.String()) } }
func Test_Run(t *testing.T) { tests := []struct { name string n1 string n2 string expRetCode int expSum float64 }{ {"2 ints", "5", "5", 0, 10}, {"2 floats", "3.2", "2.4", 0, 5.6}, {"blank", "", "", 1, 0}, {"first text", "r", "4", 1, 0}, {"second text", "4", "r", 1, 0}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { in := bytes.NewBufferString(fmt.Sprintf("%s\n%s\n", test.n1, test.n2)) out := bytes.NewBuffer([]byte{}) exp := fmt.Sprintln("The sum is:", test.expSum) if test.expRetCode != 0 { exp = "" } retCode := run(in, out) t.Log("Message:", out.String()) if test.expRetCode != retCode { t.Errorf("Expected %d, got %d\n", test.expRetCode, retCode) } if exp != out.String() { t.Errorf("Expected '%s', got '%s'\n", exp, out.String()) } }) } }
$ go test -v . === RUN Test_Add --- PASS: Test_Add (0.00s) === RUN Test_askNumber --- PASS: Test_askNumber (0.00s) === RUN Test_PrintSum --- PASS: Test_PrintSum (0.00s) === RUN Test_Run === RUN Test_Run/2_ints === RUN Test_Run/2_floats === RUN Test_Run/blank === RUN Test_Run/first_text === RUN Test_Run/second_text --- PASS: Test_Run (0.00s) --- PASS: Test_Run/2_ints (0.00s) main_test.go:70: Message: The sum is: 10 --- PASS: Test_Run/2_floats (0.00s) main_test.go:70: Message: The sum is: 5.6 --- PASS: Test_Run/blank (0.00s) main_test.go:70: Message: --- PASS: Test_Run/first_text (0.00s) main_test.go:70: Message: --- PASS: Test_Run/second_text (0.00s) main_test.go:70: Message: PASS ok github.com/rgerardi/go-cli-testing/demo6 0.002s
Consider the minimal interface that implements only what is required by the function
Instead of:
func Find(f *os.File) ([]string, error)
Use:
func Find(r io.Reader) ([]string, error)
type myStruct struct { data []byte } func newFromFile(filename string) (*myStruct, error) { data, err := ioutil.ReadFile(filename) if err != nil { return nil, err } return &myStruct{data: data}, nil } func (m *myStruct) save(filename string) error { return ioutil.WriteFile(filename, m.data, 0644) } func (m *myStruct) doMagic() { m.data = bytes.ToUpper(m.data) }
func Test_DoMagic(t *testing.T) { inStr := "Some test Data..." m := myStruct{ data: []byte(inStr), } exp := strings.ToUpper(inStr) m.doMagic() if exp != string(m.data) { t.Errorf("Expected '%s', got '%s'\n", exp, m.data) } }
Directory and file names that begin with "." or "_" are ignored by the go tool, as are directories named "testdata"
$ cat testdata/test1.txt Some dummy data for test purposes only
func Test_NewFromFile(t *testing.T) { exp := "Some dummy data for test purposes only\n\n" m, err := newFromFile("./testdata/test1.txt") if err != nil { t.Fatalf("There should be no error. Got %s\n", err) } if exp != string(m.data) { t.Errorf("Expected '%s', got '%s'\n", exp, m.data) } }
func Test_NewFromFile_TempFile(t *testing.T) { tempFile, err := ioutil.TempFile(os.TempDir(), "test1_") if err != nil { t.Fatalf("Cannot create temp file: %s\n", err) } defer os.Remove(tempFile.Name()) exp := []byte("Some test data.\n") if _, err := tempFile.Write(exp); err != nil { t.Fatalf("Cannot write to temp file: %s\n", err) } if err := tempFile.Close(); err != nil { t.Fatalf("Cannot close temp file: %s\n", err) } m, err := newFromFile(tempFile.Name()) if err != nil { t.Fatalf("There should be no error. Got %s\n", err) } if bytes.Compare(exp, m.data) != 0 { t.Errorf("Expected '%s', got '%s'\n", exp, m.data) } }
func createTempFile(t *testing.T, data []byte) (filename string, cleanFunc func()) { t.Helper() tempFile, err := ioutil.TempFile(os.TempDir(), "test2_") if err != nil { t.Fatalf("Cannot create temp file: %s\n", err) } if _, err := tempFile.Write(data); err != nil { t.Fatalf("Cannot write to temp file: %s\n", err) } if err := tempFile.Close(); err != nil { t.Fatalf("Cannot close temp file: %s\n", err) } return tempFile.Name(), func() { os.Remove(tempFile.Name()) } } func Test_NewFromFile_TempFile_Helper(t *testing.T) { exp := []byte("Some test data.\n") tempFile, cleanFunc := createTempFile(t, exp) defer cleanFunc() m, err := newFromFile(tempFile) if err != nil { t.Fatalf("There should be no error. Got %s\n", err) } if bytes.Compare(exp, m.data) != 0 { t.Errorf("Expected '%s', got '%s'\n", exp, m.data) } }