2016-02-15 03:06:34 +00:00
|
|
|
|
### 11.2.2. 测试一个命令
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2016-10-05 05:29:05 +00:00
|
|
|
|
对于测试包`go test`是一个有用的工具,但是稍加努力我们也可以用它来测试可执行程序。如果一个包的名字是 main,那么在构建时会生成一个可执行程序,不过main包可以作为一个包被测试器代码导入。
|
2015-12-14 06:50:19 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
让我们为2.3.2节的echo程序编写一个测试。我们先将程序拆分为两个函数:echo函数完成真正的工作,main函数用于处理命令行输入参数和echo可能返回的错误。
|
2015-12-14 06:50:19 +00:00
|
|
|
|
|
2016-01-20 15:00:49 +00:00
|
|
|
|
<u><i>gopl.io/ch11/echo</i></u>
|
2015-12-14 06:50:19 +00:00
|
|
|
|
```Go
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2016-04-26 12:39:04 +00:00
|
|
|
|
在测试中我们可以用各种参数和标志调用echo函数,然后检测它的输出是否正确, 我们通过增加参数来减少echo函数对全局变量的依赖。我们还增加了一个全局名为out的变量来替代直接使用os.Stdout,这样测试代码可以根据需要将out修改为不同的对象以便于检查。下面就是echo_test.go文件中的测试代码:
|
2015-12-14 06:50:19 +00:00
|
|
|
|
|
|
|
|
|
```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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
要注意的是测试代码和产品代码在同一个包。虽然是main包,也有对应的main入口函数,但是在测试的时候main包只是TestEcho测试函数导入的一个普通包,里面main函数并没有被导出,而是被忽略的。
|
2015-12-14 06:50:19 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
通过将测试放到表格中,我们很容易添加新的测试用例。让我通过增加下面的测试用例来看看失败的情况是怎么样的:
|
2015-12-14 06:50:19 +00:00
|
|
|
|
|
|
|
|
|
```Go
|
|
|
|
|
{true, ",", []string{"a", "b", "c"}, "a b c\n"}, // NOTE: wrong expectation!
|
|
|
|
|
```
|
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
`go test`输出如下:
|
2015-12-14 06:50:19 +00:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
$ 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
|
|
|
|
|
```
|
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
错误信息描述了尝试的操作(使用Go类似语法),实际的结果和期望的结果。通过这样的错误信息,你可以在检视代码之前就很容易定位错误的原因。
|
2015-12-14 06:50:19 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
要注意的是在测试代码中并没有调用log.Fatal或os.Exit,因为调用这类函数会导致程序提前退出;调用这些函数的特权应该放在main函数中。如果真的有意外的事情导致函数发生panic异常,测试驱动应该尝试用recover捕获异常,然后将当前测试当作失败处理。如果是可预期的错误,例如非法的用户输入、找不到文件或配置文件不当等应该通过返回一个非空的error的方式处理。幸运的是(上面的意外只是一个插曲),我们的echo示例是比较简单的也没有需要返回非空error的情况。
|