本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。

安装过程略过,网上搜一大把。

介绍

本文会在一个 module 中开发一个简单的 Go package。

同时介绍 go tool(也就是 go 命令行)。

以及如何 fetch,build 和 install Go 的 modules,packages,commands。

代码组织

Go 是按 packages 来组织代码的。一个 package == 一个目录。

同一个 package 中的 functions,types,variables,和 constants 是共享的。也就是包访问权限,java 默认也是包访问权限。

packages 是放在 module 中的,module 是通过go.mod文件来定义的。典型的,一个 repository 只有一个go.mod,放在根目录。

可以使用go mod init name来创建这个文件。在 go run 后会生成go.sum文件,内容是go.mod的加密哈希。

repository 也允许有多个 module,module 的 packages 是go.mod所在的目录,如果子目录也有go.mod,那么这个子目录的 packages 就属于子目录 module。

第一个程序

假设 module path 是example.com/user/hello,

$ mkdir hello # Alternatively, clone it if it already exists in version control.
$ cd hello
$ go mod init example.com/user/hello
go: creating new go.mod: module example.com/user/hello
$ cat go.mod
module example.com/user/hello

go 1.14
$

Go 源文件的第一个语句必须是 package name。程序入口必须是 package main。

package main

import "fmt"

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

喜闻乐见 Hello World。

现在可以 build 和 install,

$ go install example.com/user/hello
$

这条命令会 build 然后生成可执行二进制文件(这是我比较喜欢 Go 的一个原因,直接生成可执行文件,省去了安装依赖的麻烦)。

build和install命令都可以生成可执行文件。不同点在于

  • go build 不能生成包文件, go install 可以生成包文件
  • go build 生成可执行文件在当前目录下, go install 生成可执行文件在 bin 目录下

install 生成文件的 bin 目录是根据环境变量来的。按以下顺序检查

  • GOBIN
  • GOPATH

如果都没有设置,就会生成到默认 GOPATH(Linux $HOME/go 或 Windows %USERPROFILE%\go)。

示例的二进制文件会生成到$HOME/go/bin/hello(Windows 的话就是%USERPROFILE%\go\bin\hello.exe)。

可以查看环境变量 GOBIN 和 GOPATH 的值

go env

也可以设置GOBIN

$ go env -w GOBIN=/somewhere/else/bin
$

设置后可以重置

$ go env -u GOBIN
$

GOPATH需要到系统环境变量进行修改。

install 等命令需要在源文件目录下执行,准确点说是“当前工作目录”。否则会报错。

在当前目录执行,以下等价

$ go install example.com/user/hello
$ go install .
$ go install

验证下结果,为了方便,添加 install 目录到PATH

## Windows users should consult https://github.com/golang/go/wiki/SettingGOPATH
## for setting %PATH%.
$ export PATH=$PATH:$(dirname $(go list -f '{{.Target}}' .))
$ hello
Hello, world.
$

如果 cd 到了 install 的 bin 目录,也可以直接

$ hello
Hello, world.
$

现阶段 Go 的很多库都是放在 GitHub 等代码托管网站上面的,使用 Git 进行提交

$ git init
Initialized empty Git repository in /home/user/hello/.git/
$ git add go.mod hello.go
$ git commit -m "initial commit"
[master (root-commit) 0b4507d] initial commit
 1 file changed, 7 insertion(+)
 create mode 100644 go.mod hello.go
$

Go 命令通过请求相应的 HTTPS URL,并读取嵌入在 HTML 响应中的元数据<meta>标签,来定位包含给定 module path 的 repository

Bitbucket (Git, Mercurial)

	import "bitbucket.org/user/project"
	import "bitbucket.org/user/project/sub/directory"

GitHub (Git)

	import "github.com/user/project"
	import "github.com/user/project/sub/directory"

Launchpad (Bazaar)

	import "launchpad.net/project"
	import "launchpad.net/project/series"
	import "launchpad.net/project/series/sub/directory"

	import "launchpad.net/~user/project/branch"
	import "launchpad.net/~user/project/branch/sub/directory"

IBM DevOps Services (Git)

	import "hub.jazz.net/git/user/project"
	import "hub.jazz.net/git/user/project/sub/directory"

很多托管网站已经为 Go 的 repository 提供了元数据,为了共享 module,最简单的办法就是让 module path 匹配 repository 的 URL。

从 module import packages

先在名字为 morestrings 的 package 中创建一个reverse.go文件,实现字符串反转

// Package morestrings implements additional functions to manipulate UTF-8
// encoded strings, beyond what is provided in the standard "strings" package.
package morestrings

// ReverseRunes returns its argument string reversed rune-wise left to right.
func ReverseRunes(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)
}

由于 ReverseRunes 函数是大写的,所以是公有的,可以被其他 packages import。

先 build 测试下编译成功

$ cd $HOME/hello/morestrings
$ go build
$

因为只是在 package 中,不是在 module 根目录,go build不会生成文件,而是会把 compile 后的 package 保存到 local build cache 中。

接着在 hello.go 中 import

package main

import (
	"fmt"

	"example.com/user/hello/morestrings"
)

func main() {
	fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
}

然后 install hello

$ go install example.com/user/hello

验证,import 成功,字符串反转

$ hello
Hello, Go!

从远程 remore modules import packages

可以用 import path 通过版本控制系统来获取 package 源码,如 Git 或 Mercurial。

示例,使用github.com/google/go-cmp/cmp

package main

import (
	"fmt"

	"example.com/user/hello/morestrings"
	"github.com/google/go-cmp/cmp"
)

func main() {
	fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
	fmt.Println(cmp.Diff("Hello World", "Hello Go"))
}

当运行命令go install go build go run的时候,go 命令会自动下载远程 module,然后写到go.mod文件中

$ go install example.com/user/hello
go: finding module for package github.com/google/go-cmp/cmp
go: downloading github.com/google/go-cmp v0.4.0
go: found github.com/google/go-cmp/cmp in github.com/google/go-cmp v0.4.0
$ hello
Hello, Go!
  string(
- 	"Hello World",
+ 	"Hello Go",
  )
$ cat go.mod
module example.com/user/hello

go 1.14

require github.com/google/go-cmp v0.4.0
$

国内容易超时,可以使用代理走国内镜像

七牛云

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

阿里云

go env -w GO111MODULE=on
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct

module 依赖会自动下载到GOPATH指定目录的pkg/mod子目录。

module 指定版本的下载内容,是在所有其他 require 这个版本的 modules 中共享的,所以 go 命令会标记这些文件和目录为只读的。

可以使用命令删除所有下载的 modules

$ go clean -modcache
$

测试

Go 有个轻量的测试框架,go test和testing package。

测试框架识别以_test.go结尾的文件,包含TestXXX命名的函数,函数签名func (t *testing.T)。如果函数调用失败如t.Error 或 t.Fail,那么 test 就会失败。

示例,新建*$HOME/hello/morestrings/reverse_test.go*文件,添加morestrings package 的测试代码

package morestrings

import "testing"

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

运行测试

$ go test
PASS
ok  	example.com/user/morestrings 0.165s
$