GOROOT VS GOPATH VS GOBIN VS GOPROXY

  • GOROOT: Go语言安装路径
  • GOPATH: 若干工作区目录的路径。是我们自己定义的工作空间
    • GO1.8版本之后,开发包安装完成后会自动设置一个GOPATH目录,
    • GO1.14版本之后,推荐使用Go Module模式,不一定非要将代码写在GOPATH目录下,也不需要自己配置GOPATH
  • GOBIN: GO程序生成的可执行文件的路径
  • GOPROXY: 默认为GOPROXY=https://proxy.golang.org,direct,修改为GOPROXY=https://goproxy.cn,direct

跨平台编译

Windows 编译 LINUX或者OSX

1
2
3
4
SET CGO_ENABLED=0   // 禁用CGO
SET GOOS=linux // 目标平台, [windows, linux, darwin]
SET GOARCH=amd64 // 目标处理器架构
go build

LINUX 或者 OSX 编译其他环境

1
CGO_ENABLED=0 GOOS=linux|darwin|windows GOARCH=amd64 go build
  • 只有Windows 需要在cmd窗口中运行SET命令

编写测试

  1. 有一个程序名为xxx.go,则其测试程序应该名为xxx_test.go
  2. 测试函数的命名以Test开始,例如Testxxx()
  3. 测试函数只能有一个参数t *testing.T,参数t是测试的hook,测试失败时可以执行t.Fail()等操作

举例

  • 有一个main函数,打印Hello world
1
2
3
4
5
6
7
8
9
10
11
12
// hello.go
package main

import "fmt"

func Hello() string {
return "Hello world"
}

func main() {
fmt.Println(Hello())
}
  • 则对应的测试代码应该为
1
2
3
4
5
6
7
8
9
10
11
12
// hello_test.go
package main

import "testing"

func TestHello(t *testing.T){
got := Hello()
want := "Hello world"
if got != want{
t.Errorf("got '%q' want '%q'", got, want)
}
}
  • 在终端直接执行go test命令即可进行测试,如果文件名错误,会报错:? gogo [no test files]

重构 t.Helper()

现在有这两个文件hello.go, hellotest.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// hello.go
package main

import "fmt"

const helloPrefix = "Hello "


func Hello(name string) string {
return helloPrefix + name
}

func main() {
fmt.Println(Hello("world"))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "testing"

func TestHello(t *testing.T){
assertCorrectMessage := func(t *testing.T, got, want string){
t.Helper()
if got != want{
t.Errorf("got '%q' want '%q'", got, want)
}
}
// 成功
t.Run("saying hello to people", func(t *testing.T){
got := Hello("chris")
want := "Hello chris"
assertCorrectMessage(t, got, want)
})
// 失败
t.Run("say hello world when an empty string is supplied", func(t *testing.T){
got := Hello("")
want := "Hello world"
assertCorrectMessage(t, got, want)
})
}
  • 将断言单独作为一个函数,由两个子测试进行调用。
  • 第一个子测试能通过,第二个子测试失败
  • t.Helper()告诉测试套件这个函数是一个辅助函数,这样测试失败时报告的行号将在函数调用中,而不是在辅助函数内部。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 不添加t.Helper()的输出
--- FAIL: TestHello (0.00s)
--- FAIL: TestHello/say_hello_world_when_an_empty_string_is_supplied (0.00s)
hello_test.go:15: got '"Hello "' want '"Hello world"' # 断言函数处的行号,但是具体不知道是哪个测试用例出错
FAIL
exit status 1
FAIL gogo 0.001s

# 添加t.Helper()的输出
--- FAIL: TestHello (0.00s)
--- FAIL: TestHello/say_hello_world_when_an_empty_string_is_supplied (0.00s)
hello_test.go:28: got '"Hello "' want '"Hello world"' # 具体测试用例内部的行号
FAIL
exit status 1
FAIL gogo 0.001s

命名返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func Hello(name string, language string) string {
if name == ""{
name = "world"
}
return greetingPrefix(language) + name
}

func greetingPrefix(language string) (prefix string){
switch language {
case french:
prefix = frenchHelloPrefix
case spanish:
prefix = spanishHelloPrefix
default:
prefix = englishHelloPrefix
}
return
}
  • 在函数签名中,使用了命名返回值prefix string
    • 会自动创建一个名为prefix的变量,并且分配零值,即如果是int,则prefix=0,如果是string,则prefix=""
    • 会在Go Doc中显示,代码更加清晰
    • 只需要直接写return即可,不需要return prefix
  • 函数如果是小写字母开头,则是私有函数;如果是大写字母开头,则是公共函数

示例

1
2
3
4
5
6
// integer.go

// Add takes two integers and returns the sum of them
func Add(a, b int) int {
return a + b
}
  • Add函数有两个相同类型的参数,所以可以直接写成x, y int
  • 添加的函数注释Add takes two integers and returns the sum of them会放在Go Doc中
  • 添加示例函数,示例函数同样会更新在Go Doc中,可以反映出代码的实际功能
1
2
3
4
5
6
// integer_test.go
func ExampleAdd(){
sum := Add(1, 2)
fmt.Println(sum)
// Output: 3
}
  • // Output: 3实际上是ExampleAdd()这个函数的期望输出,如果写5,表明希望1 + 2 = 5,会返回测试失败
  • 这个语法不能在别的测试中使用,只有Examplexxx()中可以使用
  • 使用go test -v可以输出每个测试用例的通过情况,以及整体的通过情况,输出结果如下
1
2
3
4
5
6
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
=== RUN ExampleAdd
--- PASS: ExampleAdd (0.00s)
PASS
ok integer 0.001s

基准测试

1
2
3
4
5
6
7
8
// iter.go
func Repeat(a string) string{
var repeat string
for i := 0; i < 5; i++{
repeat += a
}
return repeat
}
  • 编写基准测试
1
2
3
4
5
6
// iter_test.go
func BenchmarkRepeat(b *testing.B){
for i := 0; i < b.N; i++{
Repeat("a")
}
}
  • testing.B可以访问隐性命名b.N,表示这个代码的运行次数,并记录时间
  • 测试框架会选择b.N
  • 使用go test -bench=.来运行基准测试,如果在Windows中则使用go test -bench="."
  • 如果直接使用go test [-v]不会运行基准测试
  • 使用go test -cover查看覆盖率

数组 VS 切片

  • numbers := [5]int{1,2,3,4,5}
  • numbers := [...]int{1,2,3,4,5}
  • 遍历数组的两个方式
1
2
3
4
5
6
7
8
for i := 0; i < 5; i ++{
...
}

for _, number := range numbers{
...
}
// range返回索引和值,使用空白标志符来忽略索引
  • 数组传参,比如需要传入numbers,参数类型应该为numbers [5]int
  • 此时如果传入一个[4]int传入函数,不能通过编译,因为会判定为不同的类型
  • 所以导致数组没什么用,一般用切片slice,尺寸不固定
  • 切片就是在声明的时候不指定长度,也就是mySlice := []int{1,2,3},而不是mySlice := [3]int{1,2,3}
  • 参数是可变数量的切片时,应该写numbers ... []int
  • 不能对切片使用等于号,简单的方法是使用reflect.DeepEqual,用于判断两个变量是否相等
  • 但是reflect.DeepEqual不是类型安全的,甚至可以比较slicestring
  • 使用make创建切片可以指定容量和长度,创建的新切片中所有元素均为0