Go开发环境配置

​ 选择操作系统对应的版本,可以直接下载安装包进行一键安装,也可以下载tar/zip包进行自定义安装。自定义安装时需要在环境变量中指定GOROOTGOPATHGOROOT是tar包的解压目录,GOPATH是Go工程项目目录,这里不仅存储这自己开发的项目也存放着项目所依赖包的源码及二进制文件。

Go项目目录

​ 使用某种编程语言或者某个编程框架开发的项目都有自己特有的目录结构,所以学习一门新语言或者编程框架时要先熟悉其项目的工程目录结构。Go的工程目录是一个层级目录,第一层有两个目录binsrc两个目录,bin文件中存放的是Go编译之后的可执行文件,src文件夹中存放的都是项目源文件,项目的目录结构可根据代码功能进行划分。Go工程项目结构样例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bin/
hello # command executable
outyet # command executable
src/
github.com/golang/example/
.git/ # Git repository metadata
hello/
hello.go # command source
outyet/
main.go # command source
main_test.go # test source
stringutil/
reverse.go # package source
reverse_test.go # test source
golang.org/x/image/
.git/ # Git repository metadata
bmp/
reader.go # package source
writer.go # package source
... (many more repositories and packages omitted) ...

上面的src目录有两个Go项目,分别是exampleimageexample中有三个文件夹,其中hellooutyet分别包含可执行命令的文件,stringutil是一个工具包。image中没有包含可执行命令的文件,只有一个bmp的工具包和其它工具包。通过go install命令编译example项目会bin目录中增加两个可执行文件hellooutyet

​ Go的工程目录其实就是在上一节中设置的GOPATH,默认目录名是go,会当前用户home目录下创建此文件夹,例如Linux为$HOME/go,win为C:\Users\YourName\go。可以通过export GOPATH=xx设置任意目录,不过不能是Go的安装目录,也就是GOROOT

​ Go提供了env命令,它可以输出环境变量的值,例如执行命令go env GOPATH,如果为了执行Go编译的可执行文件方便,可将GOPATH目录添加到PATH环境变量里,增加export PATH=$PATH:$(go env GOPATH)/bin

Go Go Go!

​ 环境部署好,也熟悉了Go项目的目录结构,下面来个Go Go Go!找下Hello World的感觉。在$GOPATH/src目录下创建项目文件夹go-test,然后在此目录新建一个hello.go的文件,内容如下:

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("Hello, Go Go Go!")
}

如果是在IDE中可以直接点击右键执行,也可以在命令行执行go run hello.go 执行。Go可以生成可执行文件执行运行,执行命令go install go-test,其中go-test是项目相对于GOPATH的相对路径,也可以直接在go-test目录下执行go install,此命令成功之后会在$GOPATH/bin目录下增加一个go-test的可执行文件(如果是win,则是go-test.exe)。go install没有任何输出代表执行成功,执行异常时会有错误输出,例如:

1
2
3
4
➜  go-test go install
# go-test
./t.go:3:6: main redeclared in this block
previous declaration at ./hello.go:8:6

执行成功可以直接执行$GOPATH/bin/go-test

​ Go和其它语言类似,也需要在文件开头引入一些包,使用命令import,例如hello.go中的import "fmt"fmt是Go自带的标准依赖包,如果要引用其它依赖包则需要依赖包在$GOPATH中的相对路径,这个相对路径是每个依赖包的唯一标识。

​ 由于Go语言天生的开源特性,很多依赖包的开源作者都习惯使用GitHub来管理自己的代码,在import时写github.com/user/pro即可,而且还可以选择不同的版本,方便的很。所以我们也可以在$GOPATH中创建自己的GitHub目录(即使你没有GitHub账号,但为了方便你某一天想把自己的代码开源出去让更多的人使用),假如Github账号是go-test,创建目录mkdir -p $GOPATH/src/github.com/go-test,然后将上面的hello.go代码移到该目录中。

​ 如果在代码中引入了Github上的第三方依赖包,使用go get github.com/user/pro下载依赖到本地$GOPATH中,如何引入自己开发的第三方依赖包呢或者如何开发一个依赖包。

​ 开发一个依赖包首先想一个包名,这个包名在自己开发的项目中不能重复,因为Go导入第三方依赖时是导入包路径。这里给hello.go增加一个stringutil的依赖,创建目录mkdir $GOPATH/src/github.com/go-test/stringutil,在此目录中新建一个 reverse.go的文件,复制如下代码:

1
2
3
4
5
6
7
8
9
10
11
// Package stringutil contains utility functions for working with strings.
package stringutil

// Reverse returns its argument string reversed rune-wise left to right.
func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}

依赖包使用 go build进行编译,命令执行之后与go install一样不会打印任何信息,不同的是go install会在$GOPATH/bin中输出一个二进制文件,而 go build没有任何输出,只是将编译的结果放入本地build cache中。

​ 依赖包编译成功之后就可以引用了,在上述的hello.go中添加依赖,代码如下:

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"github.com/go-test/stringutil"
)

func main() {
fmt.Println("Hello, Go Go Go!")
fmt.Println(stringutil.Reverse("!oG ,olleH"))
}

执行go install,随后就可以执行go-test

​ 写完上面的代码,此时可能有些疑问为什么hello.go的package是main,而 reverse.go的package是stringutil。Go代码的第一行必须是package name,这个name一般为当前文件所在的目录名,例如reverse.gostringutil目录中,则第一行为package stringutil,这个name也是依赖此包时导入路径的最后一个目录,如hello.go中要依赖的源码是 reverse.go,而它在stringutil目录中,所以添加import "github.com/go-test/stringutil"。需要注意的是某个package中的所有文件必须有一样的name,而且如果包含可执行命令,name必须为main,也就是像hello.go那样,而且还得包含func main()

Test

​ 了解上述内容其实就可以去玩Go了,但是在玩的过程中可能会遇到给文件起名字时以 _test.go的问题,在其它语言中对文件名字的内容并没有限制,但在Go中有限制,如果以 _test.go结尾则代表这是一个测试类。

​ Go有一个轻量的测试框架,使用时导入 testing包,执行go test 。使用测试框架时,创建一个以_test.go结尾文件,测试某个func的方法名为func TestXXX(t *testing.T),如果这个测试方法调用了 t.Error 或者 t.Fail则认为测试失败。给上述 reverse.go添加一个测试类,在stringutil文件中创建reverse_test.go,复制如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package stringutil

import "testing"

func TestReverse(t *testing.T) {
cases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{"Hello, 世界", "界世 ,olleH"},
{"", ""},
}
for _, c := range cases {
got := Reverse(c.in)
if got != c.want {
t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want)
}
}
}

然后执行go test github.com/go-test/stringutil或者直接在stringutil目录中执行go test,执行成功输出如下:

1
2
$ go test github.com/go-test/stringutil
ok github.com/user/stringutil 0.165s