在进行Go语言具体内容学习之前,让我们以实际的程序一起来从整体上解下Go语言程序,包括: Go语言程序结构,Go语言包规则,Go程序编译、运行、测试以及程序文档使用。
$GOPATH/
├── bin
│ ├── dlv
│ ├── gocode
│ ├── main
├── pkg
│ └── darwin_amd64
│ ├── github.com
│ │ └── peterh
│ └── zhongxiao.yzx
│ └── add.a
└── src
├── github.com
│ ├── peterh
│ │ └── liner
└── zhongxiao.yzx
├── add
| ├── example_test.go
│ └── add.go
└── test
| ├── benchmark_test.go
| └── ut_test.go
└── main.go
如上所示的Go程序结构以$GOPATH为根目录,对应的工作区目录有三个子目录。
// main.go
package main
import (
"fmt"
"zhongxiao.yzx/add"
)
func main() {
a, b := 3, 4
result := add.BigAdd(3, 4)
fmt.Printf("%d + %d = %d", a, b, result)
}
add.go
package add
func BigAdd(a int, b int) int {
return a + b
}
example_test.go
package add
import (
"fmt"
)
func ExampleBigAdd() {
fmt.Println(BigAdd(3, 4))
fmt.Println(BigAdd(4, 4))
// Output:
// 7
// 8
}
ut_test.go
package test
import (
"testing"
"zhongxiao.yzx/add"
)
func Test_BigAdd(t *testing.T) {
a, b := 3, 3
result := add.BigAdd(a, b)
if 6 != result {
t.Errorf("%d + %d = %d %d expected", a, b, result, 6)
}
}
benchmark_test.go
package test
import (
"fmt"
"testing"
"zhongxiao.yzx/add"
)
func Benchmark_BigAdd(b *testing.B) {
fmt.Println("Benchmark_Test")
for i := 0; i < b.N; i++ {
add.BigAdd(i, i)
}
}
一个Go语言编写的程序对应一个或多个以.go为文件后缀名的源文件中(如上示例代码)。
构造以上的Go工程,进入自定义包目录zhongxiao.yzx,通过如下的命令和过程我们可以编译,运行及测试Go程序。
go test
go test命令会遍历所有的*_test.go文件中符合命名规则的函数,然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件。
采用如上所示工程作为go test示例,包目录内所有以*_test.go为后缀名的源文件并不是go build构建包的一部分,它们是go test测试的一部分。
在*_test.go文件中,有三种类型的函数:
测试函数
一个测试函数是以Test为函数名前缀的函数,用于测试程序的一些逻辑行为是否正确;go test命令会调用这些测试函数并报告测试结果是PASS或FAIL。
ut_test.go
package test
import (
"testing"
"zhongxiao.yzx/add"
)
func Test_BigAdd(t *testing.T) {
a, b := 3, 3
result := add.BigAdd(a, b)
if 6 != result {
t.Errorf("%d + %d = %d %d expected", a, b, result, 6)
}
}
在zhongxiao.yzx/test目录下执行如下的命令可以运行测试函数
go test -coverprofile=c.out //运行test并生成覆盖率profile
go tool cover -html=c.out // c.out profile生成html```
基准测试函数
基准测试函数是以Benchmark为函数名前缀的函数,它们用于衡量一些函数的性能;go test命令会多次运行基准函数以计算一个平均的执行时间。
benchmark_test.go
package test
import (
"fmt"
"testing"
"zhongxiao.yzx/add"
)
func Benchmark_BigAdd(b *testing.B) {
fmt.Println("Benchmark_Test")
for i := 0; i < b.N; i++ {
add.BigAdd(i, i)
}
}
在zhongxiao.yzx/test目录下执行
go test -bench=. // 执行基准测试
默认情况下不运行任何基准测试。需要通过 -bench 命令行标志参数手工指定要运行的基准测试函数。该参数是一个正则表达式,用于匹配要执行的基准测试函数的名字,默认值是空的。其中“.”模式将可以匹配所有基准测试函数。
示例函数
示例函数是以Example为函数名前缀的函数,提供一个由编译器保证正确性的示例文档。
func ExampleBigAdd() {
fmt.Println(BigAdd(3, 4))
fmt.Println(BigAdd(4, 4))
// Output:
// 7
// 8
}
从上面的示例程序可见,包是组织Go语言工程的基本单元。Go语言中的包和其他语言的库或模块的概念类似,目的都是为了支持模块化、封装、单独编译和代码重用。一个包的源代码保存在一个或多个以.go为文件后缀名的源文件中,通常一个包所在目录路径的后缀是包的导入路径;例如包gopl.io/ch1/helloworld对应的目录路径是$GOPATH/src/gopl.io/ch1/helloworld。
func init() { /* ... */ }
这样的init初始化函数除了不能被调用或引用外,其他行为和普通函数类似。在每个文件中的init初始化函数,在程序开始执行时按照它们声明的顺序被自动调用。每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次。
import (
"crypto/rand"
mrand "math/rand" // alternative name mrand avoids conflict
import _ "image/png" // 匿名导入 register PNG decoder
)
如果我们想同时导入两个有着相同名字的包,例如math/rand包和crypto/rand包,那么导入声明必须至少为一个同名包指定一个新的包名以避免冲突。导入包的重命名只影响当前的源文件。其它的源文件如果导入了相同的包,可以用导入包原本默认的名字或重命名为另一个完全不同的名字。
如果只是导入一个包而并不使用导入的包将会导致一个编译错误。有时候我们只是想利
用导入包计算包级变量的初始化表达式和执行导入包的init初始化函数, 我们可以用下划线 _ 来重命名导入的包。这个被称为包的匿名导入。它通常是用来实现一个编译时机制,然后通过在main主程序入口选择性地导入附加的包。
import (
"database/sql"
_ "github.com/lib/pq" // enable support for Postgres
_ "github.com/go-sql-driver/mysql" // enable support for MySQL
)
db, err = sql.Open("postgres", dbname) // OK
db, err = sql.Open("mysql", dbname) // OK
db, err = sql.Open("sqlite3", dbname) // returns error: unknown driver "sqlite3"
test
前缀的源文件是被忽略的),并且这些源文件声明的包名也是以_test为后缀名的。所有以_test为后缀包名的测试外部扩展包都由go test命令独立编译。在Go语言中,Go为我们提供了快速生成文档以及查看文档的工具,让我们可以很容易的编写查看文档。
Go提供了两种查看文档的方式,如下工程为例
$Workspace/dev/golang/src/
└── zhongxiao.yzx
├── add
| ├── example_test.go
│ └── add.go
└── test
├── benchmark_test.go
└── ut_test.go
package add // import "zhongxiao.yzx/add"
func BigAdd(a int, b int) int
或者通过godoc命令查看
zhongxiao.yzx@MacBook-Pro:~/Workspace/dev/golang/src/zhongxiao.yzx$godoc cmd/zhongxiao.yzx/add
PACKAGE DOCUMENTATION
package add
import "."
FUNCTIONS
func BigAdd(a int, b int) int
godoc -http参数指定Web服务监听的IP和Port,运行后,我们就可以打开浏览器,输入http://localhost:8080/pkg/进行访问了。
如下图所以,你会发现打开的页面,和GoLang的官方网站一样,但是文档中增加了本地的包,这些包是基于本地GOROOT和GOPATH这两个路径下的所有包生成的文档。
示例程序中zhongxiao.yzx/add下有如下2个文件
add.go
package add
func BigAdd(a int, b int) int {
return a + b
}
example_test.go
func ExampleBigAdd() {
fmt.Println(BigAdd(3, 4))
fmt.Println(BigAdd(4, 4))
// Output:
// 7
// 8
}
godoc工具根据example_test.go示例程序,将一个示例函数关联到某个具体函数或包本身,因此ExampleBigAdd示例函数将是BigAdd函数文档的一部分,Example示例函数将是包文档的一部分。(example_test.go必须以_test.go作为后缀,否则go工具会将该文件作为普通的文件对待,详见go test测试部分内容)
[1]: The Go Programming Language : https://golang.org/
[2]: The Go Programming Language : http://www.gopl.io/
[3]: Go Packages : https://godoc.org/