diff --git a/ch11/ch11-02-2.md b/ch11/ch11-02-2.md index 3d33ea2..e7c4b12 100644 --- a/ch11/ch11-02-2.md +++ b/ch11/ch11-02-2.md @@ -1,3 +1,108 @@ ### 11.2.2. 測試一個命令 -TODO + +对于测试包 `go test` 是一个的有用的工具, 但是稍加努力我们也可以用它来测试可执行程序. 如果一个包的名字是 main, 那么在构建时会生成一个可执行程序, 不过 main 包可以作为一个包被测试器代码导入. + +让我们为 2.3.2节 的 echo 程序编写一个测试. 我们先将程序拆分为两个函数: echo 函数完成真正的工作, main 函数用于处理命令行输入参数和echo可能返回的错误. + +```Go +gopl.io/ch11/echo +// Echo prints its command-line arguments. +package main + +import ( + "flag" + "fmt" + "io" + "os" + "strings" +) + +var ( + n = flag.Bool("n", false, "omit trailing newline") + s = flag.String("s", " ", "separator") +) + +var out io.Writer = os.Stdout // modified during testing + +func main() { + flag.Parse() + if err := echo(!*n, *s, flag.Args()); err != nil { + fmt.Fprintf(os.Stderr, "echo: %v\n", err) + os.Exit(1) + } +} + +func echo(newline bool, sep string, args []string) error { + fmt.Fprint(out, strings.Join(args, sep)) + if newline { + fmt.Fprintln(out) + } + return nil +} +``` + +在测试中吗我们可以用各种参数和标标志调用 echo 函数, 然后检测它的输出是否正确, 我们通过增加参数来减少 echo 函数对全局变量的依赖. 我们还增加了一个全局名为 out 的变量来替代直接使用 os.Stdout, 这样测试代码可以根据需要将 out 修改为不同的对象以便于检查. 下面就是 echo_test.go 文件中的测试代码: + +```Go +package main + +import ( + "bytes" + "fmt" + "testing" +) + +func TestEcho(t *testing.T) { + var tests = []struct { + newline bool + sep string + args []string + want string + }{ + {true, "", []string{}, "\n"}, + {false, "", []string{}, ""}, + {true, "\t", []string{"one", "two", "three"}, "one\ttwo\tthree\n"}, + {true, ",", []string{"a", "b", "c"}, "a,b,c\n"}, + {false, ":", []string{"1", "2", "3"}, "1:2:3"}, + } + for _, test := range tests { + descr := fmt.Sprintf("echo(%v, %q, %q)", + test.newline, test.sep, test.args) + + out = new(bytes.Buffer) // captured output + if err := echo(test.newline, test.sep, test.args); err != nil { + t.Errorf("%s failed: %v", descr, err) + continue + } + got := out.(*bytes.Buffer).String() + if got != test.want { + t.Errorf("%s = %q, want %q", descr, got, test.want) + } + } +} +``` + +要注意的是测试代码和产品代码在同一个包. 虽然是main包, 也有对应的 main 入口函数, 但是在测试的时候 main 包只是 TestEcho 测试函数导入的一个普通包, 里面 main 函数并没有被导出是被忽略的. + +通过将测试放到表格中, 我们很容易添加新的测试用例. 让我通过增加下面的测试用例来看看失败的情况是怎么样的: + +```Go +{true, ",", []string{"a", "b", "c"}, "a b c\n"}, // NOTE: wrong expectation! +``` + +`go test` 输出如下: + +``` +$ go test gopl.io/ch11/echo +--- FAIL: TestEcho (0.00s) + echo_test.go:31: echo(true, ",", ["a" "b" "c"]) = "a,b,c", want "a b c\n" +FAIL +FAIL gopl.io/ch11/echo 0.006s +``` + +错误信息描述了尝试的操作(使用Go类似语法), 实际的行为, 和期望的行为. 通过这样的错误信息, 你可以在检视代码之前就很容易定位错误的原因. + +要注意的是在测试代码中并没有调用 log.Fatal 或 os.Exit, 因为调用这类函数会导致程序提前退出; 调用这些函数的特权应该放在 main 函数中. 如果真的有以外的事情导致函数发送 panic, 测试驱动应该尝试 recover, 然后将当前测试当作失败处理. 如果是可预期的错误, 例如非法的用户输入, 找不到文件, 或配置文件不当等应该通过返回一个非空的 error 的方式处理. 幸运的是(上面的意外只是一个插曲), 我们的 echo 示例是比较简单的也没有需要返回非空error的情况. + +