diff --git a/eBook/13.10.md b/eBook/13.10.md new file mode 100644 index 0000000..1b20f45 --- /dev/null +++ b/eBook/13.10.md @@ -0,0 +1,2 @@ +# 13.10 性能调试:分析并优化 Go 程序 + diff --git a/eBook/13.6.md b/eBook/13.6.md index 14d6ab5..8787d93 100644 --- a/eBook/13.6.md +++ b/eBook/13.6.md @@ -1 +1,88 @@ -# 13.6 启动外部命令和程序 \ No newline at end of file +# 13.6 启动外部命令和程序 + +os 包有一个 `StartProcess` 函数可以调用或启动外部系统命令和二进制可执行文件;它的第一个参数是要运行的进程,第二个参数用来传递选项或参数,第三个参数是含有系统环境基本信息的结构体。 + +这个函数返回被启动进程的 id(pid),或者启动失败返回错误。 + +exec 包中也有同样功能的更简单的结构体和函数;主要是 `exec.Command(name string, arg ...string)` 和 `Run()`。首先需要用系统命令或可执行文件的名字创建一个 `Command` 对象,然后用这个对象作为接收者调用 `Run()`。下面的程序(因为是执行 Linux 命令,只能在 Linux 下面运行)演示了它们的使用: + +示例 13.6 [exec.go](examples/chapter_13/exec.go): + +```go +// exec.go +package main +import ( + "fmt" + "os/exec" + "os" +) + +func main() { +// 1) os.StartProcess // +/*********************/ +/* Linux: */ +env := os.Environ() +procAttr := &os.ProcAttr{ + Env: env, + Files: []*os.File{ + os.Stdin, + os.Stdout, + os.Stderr, + }, + } +// 1st example: list files +pid, err := os.StartProcess("/bin/ls", []string{"ls", "-l"}, procAttr) +if err != nil { + fmt.Printf("Error %v starting process!", err) // + os.Exit(1) +} +fmt.Printf("The process id is %v", pid) +``` + +输出: + +``` go +The process id is &{2054 0}total 2056 +-rwxr-xr-x 1 ivo ivo 1157555 2011-07-04 16:48 Mieken_exec +-rw-r--r-- 1 ivo ivo 2124 2011-07-04 16:48 Mieken_exec.go +-rw-r--r-- 1 ivo ivo 18528 2011-07-04 16:48 Mieken_exec_go_.6 +-rwxr-xr-x 1 ivo ivo 913920 2011-06-03 16:13 panic.exe +-rw-r--r-- 1 ivo ivo 180 2011-04-11 20:39 panic.go +``` + +```go +// 2nd example: show all processes +pid, err = os.StartProcess("/bin/ps", []string{"-e", "-opid,ppid,comm"}, procAttr) +if err != nil { + fmt.Printf("Error %v starting process!", err) // + os.Exit(1) +} +fmt.Printf("The process id is %v", pid) + +``` + +```go +// 2) exec.Run // +/***************/ +// Linux: OK, but not for ls ? +// cmd := exec.Command("ls", "-l") // no error, but doesn't show anything ? +// cmd := exec.Command("ls") // no error, but doesn't show anything ? + cmd := exec.Command("gedit") // this opens a gedit-window + err = cmd.Run() + if err != nil { + fmt.Printf("Error %v executing command!", err) + os.Exit(1) + } + fmt.Printf("The command is %v", cmd) +// The command is &{/bin/ls [ls -l] [] 0xf840000210 true [0xf84000ea50 0xf84000e9f0 0xf84000e9c0] [0xf84000ea50 0xf84000e9f0 0xf84000e9c0] [] [] 0xf8400128c0} +} +// in Windows: uitvoering: Error fork/exec /bin/ls: The system cannot find the path specified. starting process! + +``` + + +## 链接 + +- [目录](directory.md) +- 上一节:[一种用闭包处理错误的模式](13.5.md) +- 下一节:[Go 中的单元测试和基准测试](13.7.md) \ No newline at end of file diff --git a/eBook/13.7.md b/eBook/13.7.md new file mode 100644 index 0000000..9f931e2 --- /dev/null +++ b/eBook/13.7.md @@ -0,0 +1,79 @@ +# 13.7 Go 中的单元测试和基准测试 + +首先所有的包都应该有一定的必要文档,然后同样重要的是对包的测试。 + +在第 3 章中提到了 Go 的测试工具 gotest, 我们已经在 9.8 节中使用过了。这里我们会用更多的例子进行详细说明。 + +名为 testing 的包被专门用来进行自动化测试,日志和错误报告。并且还包含一些基准测试函数的功能。 + +备注:gotest 是 Unix bash 脚本,所以在 Windows 下你需要配置 MINGW 环境(参见 2.5 节);在 Windows 环境下把所有的 pkg/linux_amd64 替换成 pkg/windows。 + +对一个包做(单元)测试,需要写一些可以频繁(每次更新后)执行的小块测试单元来检查代码的正确性。于是我们必须写一些 Go 源文件来测试代码。测试程序必须属于被测试的包,并且文件名满足这种形式 `*_test.go`,所以测试代码和包中的业务代码是分开的。 + +_test 程序不会被普通的 Go 编译器编译,所以当放应用部署到生产环境时它们不会被部署;只有 gotest 会编译所有的程序:普通程序和测试程序。 + +测试文件中必须导入 "testing" 包,并写一些名字以 `TestZzz` 打头的全局函数,这里的 `Zzz` 是被测试函数的字母描述,如 TestFmtInterface,TestPayEmployees 等。 + +测试函数必须有这种形式的头部: + +```go +func TestAbcde(t *testing.T) +``` + +T 是传给测试函数的结构类型,用来管理测试状态,支持格式化测试日志,如 t.Log,t.Error,t.ErrorF 等。在函数的结尾把输出跟想要的结果对比,如果不等就打印一个错误。成功的测试则直接返回。 + +用下面这些函数来通知测试失败: + +1)```func (t *T) Fail()``` + + 标记测试函数为失败,然后继续执行(剩下的测试)。 + +2)```func (t *T) FailNow()``` + + 标记测试函数为失败并中止执行;文件中别的测试也被略过,继续执行下一个文件。 + +3)```func (t *T) Log(args ...interface{})``` + + args 被用默认的格式格式化并打印到错误日志中。 + +4)```func (t *T) Fatal(args ...interface{})``` + + 结合 先执行 3),然后执行 2)的效果。 + +运行 go test 来编译测试程序,并执行程序中所有的 TestZZZ 函数。如果所有的测试都通过会打印出 PASS。 + +gotest 可以接收一个或多个函数程序作为参数,并指定一些选项。 + +结合 --chatty 或 -v 选项,每个执行的测试函数以及测试状态会被打印。 + +例如: + +```bash +go test fmt_test.go --chatty +=== RUN fmt.TestFlagParser +--- PASS: fmt.TestFlagParser +=== RUN fmt.TestArrayPrinter +--- PASS: fmt.TestArrayPrinter +... + +``` + +testing 包中有一些类型和函数可以用来做简单的基准测试;测试代码中必须包含以 `BenchmarkZzz` 打头的函数并接收一个 `*testing.B` 类型的参数,比如: + +```go +func BenchmarkReverse(b *testing.B) { + ... +} + +``` + +命令 ```go test –test.bench=.*``` 会运行所有的基准测试函数;代码中的函数会被调用 N 次(N是非常大的数,如 N = 1000000),并展示 N 的值和函数执行的平均时间,单位为 ns(纳秒,ns/op)。如果是用 testing.Benchmark 调用这些函数,直接运行程序即可。 + +具体可以参见 14.16 节中用 goroutines 运行基准测试的例子以及练习 13.3:string_reverse_test.go + + +## 链接 + +- [目录](directory.md) +- 上一节:[启动外部命令和程序](13.6.md) +- 下一节:[测试的具体例子](13.8.md) \ No newline at end of file diff --git a/eBook/13.8.md b/eBook/13.8.md new file mode 100644 index 0000000..e784b70 --- /dev/null +++ b/eBook/13.8.md @@ -0,0 +1,137 @@ +# 13.8 测试的具体例子 + +在练习 11.2 中你写了一个叫 main_oddeven.go 的程序用来测试前 100 个整数是否是偶数。这个函数在包 even 中。 + +下面是一种可能的方案: + +示例 13.7 [even_main.go](examples/chapter_13/even_main/even_main.go): + +```go +package main + +import ( + "fmt" + "even/even" +) + +func main() { + for i:=0; i<=100; i++ { + fmt.Printf("Is the integer %d even? %v\n", i, even.Even(i)) + } +} + +``` + +上面使用了 even.go 中的 even 包: + +示例 13.8 [even/even.go](examples/chapter_13/even/even.go): + +```go +package even + +func Even(i int) bool { // Exported function + return i%2 == 0 +} + +func Odd(i int) bool { // Exported function + return i%2 != 0 +} + +``` + +在 even 包的路径下,我们创建一个名为 oddeven_test.go 的测试程序: + +示例 13.9 [even/oddeven_test.go](examples/chapter_13/even/oddeven_test.go): + +```go +package even + +import "testing" + +func TestEven(t *testing.T) { + if !Even(10) { + t.Log(" 10 must be even!") + t.Fail() + } + if Even(7) { + t.Log(" 7 is not even!") + t.Fail() + } + +} + +func TestOdd(t *testing.T) { + if !Odd(11) { + t.Log(" 11 must be odd!") + t.Fail() + } + if Odd(10) { + t.Log(" 10 is not odd!") + t.Fail() + } +} + +``` + +由于测试需要具体的输入用例且不可能测试到所有的用例(非常像一个无穷的数),所以我们必须对要使用的测试用例思考再三。 + +至少应该包括: + +- 正常的用例 +- 反面的用例(错误的输入,如用负数或字母代替数字,没有输入等) +- 边界检查用例(如果参数的取值范围是 0 到 1000,检查 0 和 1000 的情况) + +可以直接执行 go install 安装 even 或者创建一个 以下内容的 Makefile: + +```bash +include $(GOROOT)/src/Make.inc +TARG=even +GOFILES=\ + even.go\ +include $(GOROOT)/src/Make.pkg + +``` + +然后执行 make(或 gomake)命令来构建归档文件 even.a + +测试代码不能在 GOFILES 参数中引用,因为我们不希望生成的程序中有测试代码。如果包含了测试代码,go test 会给出错误提示!go test 会生成一个单独的包含测试代码的 _test 程序。 + +现在我们可以用命令:```go test```(或 ```make test```)来测试 even 包。 + +因为示例 13.5 中的测试函数不会调用 t.Log 和 t.Fail,所以会得到一个 PASS 的结果。在这个简单例子中一切都正常执行。 + +为了看到失败时的输出,把函数 TestEven 改为: + +```go +func TestEven(t *testing.T) { + if Even(10) { + t.Log(“Everything OK: 10 is even, just a test to see failed output!”) + t.Fail() + } +} + +``` + +现在会调用 t.Log 和 t.Fail,得到的结果如下: + +```go +--- FAIL: even.TestEven (0.00 seconds) +Everything OK: 10 is even, just a test to see failed output! +FAIL + +``` + +**练习 13.4:**[string_reverse_test.go](exercises/chapter_13/string_reverse_test.go) + +为练习 7.11 [string_reverse.go](exercises/chapter_7/string_reverse.go) 写一个单元测试。 + +把 string_reverse 放到自己的包 strev 中,只包含一个可导出函数 reverse。 + +实现并测试它。 + + +## 链接 + +- [目录](directory.md) +- 上一节:[Go 中的单元测试和基准测试](13.7.md) +- 下一节:[用(测试数据)表驱动测试](13.9.md) diff --git a/eBook/13.9.md b/eBook/13.9.md new file mode 100644 index 0000000..2f1e266 --- /dev/null +++ b/eBook/13.9.md @@ -0,0 +1,60 @@ +# 13.9 用(测试数据)表驱动测试 + +编写测试代码时,一个较好的办法是把测试的输入数据和期望的结果写在一起组成一个数据表:表中的每条记录都是一个含有输入和期望值的完整测试用例,有时还可以结合像测试名字这样的额外信息来让测试输出更多的信息。 + +实际测试时简单迭代表中的每条记录,并执行必要的测试。这在练习 13.4 中有具体的应用。 + +可以抽象为下面的代码段: + +```go +var tests = []struct{ // Test table + in string + out string + +}{ + {“in1”, “exp1”}, + {“in2”, “exp2”}, + {“in3”, “exp3”}, +... +} + +func TestFunction(t *testing.T) { + for i, tt := range tests { + s := FuncToBeTested(tt.in) + if s != tt.out { + t.Errorf(“%d. %q => %q, wanted: %q”, i, tt.in, s, tt.out) + } + } +} + +``` + +如果大部分函数都可以写成这种形式,那么写一个帮助函数 verify 对实际测试会很有用: + +```go +func verify(t *testing.T, testnum int, testcase, input, output, expected string) { + if input != output { + t.Errorf(“%d. %s with input = %s: output %s != %s”, testnum, testcase, input, output, expected) + } +} + +``` + +TestFunction 则变为: + +```go + +func TestFunction(t *testing.T) { + for i, tt := range tests { + s := FuncToBeTested(tt.in) + verify(t, i, “FuncToBeTested: “, tt.in, s, tt.out) + } +} + +``` + +## 链接 + +- [目录](directory.md) +- 上一节:[测试的具体例子](13.8.md) +- 下一节:[性能调试:分析并优化 Go 程序](13.10.md) \ No newline at end of file diff --git a/eBook/directory.md b/eBook/directory.md index 77c3416..dba0adc 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -131,6 +131,10 @@ - 13.4 [自定义包中的错误处理和 panicking](13.4.md) - 13.5 [一种用闭包处理错误的模式](13.5.md) - 13.6 [启动外部命令和程序](13.6.md) + - 13.7 [Go 中的单元测试和基准测试](13.7.md) + - 13.8 [测试的具体例子](13.8.md) + - 13.9 [用(测试数据)表驱动测试](13.9.md) + - 13.10 [性能调试:分析并优化 Go 程序](13.10.md) - 第14章:goroutine 与 channel - 第15章:网络、模版与网页应用