本课程培训目标
通过本课程能编写Go的Web小程序,让大家对Go有点小兴趣
哪些公司、平台在使用Go语言
B站、七牛云、K8s
Go语言优势
占用内存小,并发编程上手简单
适合容器化部署
适合C语言程序员上手
Go语言劣势
第三方类库没有java丰富
Go语言特征
1.自动立即回收
2.更丰富的内置类型
3.函数多返回值
4.错误处理
5.匿名函数和闭包
6.类型和接口
7.并发编程
8.反射
9.语言交互性
Go没有的语言特征
没有继承,不面向对象
Go语言基础语法
- 关键字import、if else、for(没有while)、switch、指针
- 特性:
- 高级特性: 协程、管道Channel
Hello world
1.安装Go,下载地址
##查看go版本
go version
2.安装IDE。可以使用VSCode(前端经验入门), Idea的GoLand(java经验入门)
3.创建文件夹go-playground,使用Idea打开这个文件夹
4.在根目录下,创建main.go
package main
import "fmt"
func hello() {
return "Hello, world"
}
func main() {
fmt.Println("hello world!")
}
- 使用go官方的包(package)依赖管理工具
go mod
,go mod等同于maven、npm、cocoapads等。包依赖管理这个我们下节课在仔细讲。
在根目录(go-playground)执行:go mod init github.com/wongben/go-playground
执行后在根目录生成文件go.mod。内容如下:
module github.com/wongben/go-playground
go 1.12
6.在命令行里执行
go build //生成go-playground.exe文件
go run main.go //编译和执行main.go
7.编写测试案例
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)
}
}
编写测试和函数很类似,其中有一些规则:
- 程序需要在一个名为 xxx_test.go 的文件中编写
- 测试函数的命名必须以单词 Test 开始
- 测试函数只接受一个参数 t *testing.T
现在这些信息让我们明白: 类型为 *testing.T 的变量 t 是你在测试框架中的 hook(钩子),所以当你想让测试失败时可以执行 t.Fail() 之类的操作。
接下来我们把测试类重构一下
package hello
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("ben", "en")
want := "hello, ben"
assertCorrectMessage(t, got, want)
})
t.Run("中文测试", func(t *testing.T) {
got := Hello("ben","cn")
want := "你好, ben"
assertCorrectMessage(t, got, want)
})
}
这里我们将使用测试库中的另一个工具 -- 子测试。有时,对一个「事情」进行分组测试,然后再对不同场景进行子测试非常有效。
这种方法的好处是,你可以建立在其他测试中也能够使用的共享代码。
当我们检查信息是否符合预期时,会有重复的代码。重构不仅仅是针对程序的代码
int整型
首先,先写测试。
这里,我们可以通过编写 示例 更深入地了解测试,标准库的文档中能够找到许多这样的例子。与典型的测试一样,示例是存在于一个包的 _test.go 文件中的函数。向 adder_test.go 文件添加以下 ExampleAdd 函数。
package integers
import (
"fmt"
"testing"
)
func ExampleAdd() {
sum := Add(1, 5)
fmt.Println(sum)
// Output: 6
}
func TestAdd(t *testing.T) {
sum := Add(2, 2)
expected := 4
if sum != expected {
t.Errorf("expected '%d', but got '%d'", expected, sum)
}
}
package integers
func Add(x, y int) int{
return x + y
}
迭代与基准测试
在 Go 中 for 用来循环和迭代,Go 语言没有 while,do,until 这几个关键字,你只能使用 for。
func main() {
sum := 1
//初始化语句和后置语句是可选的,等效于for ; sum < 1000; {
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
迭代
const repeatCount = 5
func Repeat(character string) string {
var repeated string
for i := 0; i < repeatCount; i++ {
repeated += character
}
return repeated
}
基准测试
在 Go 中编写基准测试(benchmarks)是该语言的另一个一级特性,它与编写测试非常相似。
func BenchmarkRepeat(b *testing.B) {
for i := 0; i < b.N; i++ {
Repeat("a")
}
}
你会看到上面的代码和写测试差不多。
testing.B 可使你访问隐性命名(cryptically named)b.N。基准测试运行时,代码会运行 b.N 次,并测量需要多长时间。代码运行的次数不会对你产生影响,测试框架会选择一个它所认为的最佳值,以便让你获得更合理的结果。
用 go test -bench=. 来运行基准测试。
数组与分片
- 先写测试函数
package arrays
import "testing"
func TestSum(t *testing.T) {
//var n [5]int
//numbers := [5]int{}
//numbers := [5]int{1,2,3,4,5}
//numbers := []int{1,2,3,4,5}
numbers := [...]int{1,2,3,4,5}
got := sum(numbers)
want := 15
if want != got {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
}
数组的容量是我们在声明它时指定的固定值。我们可以通过两种方式初始化数组:
[N]type{value1, value2, ..., valueN} e.g. numbers := [5]int{1, 2, 3, 4, 5}
[...]type{value1, value2, ..., valueN} e.g. numbers := [...]int{1, 2, 3, 4, 5}
在错误信息中打印函数的输入有时很有用。我们使用 %v(默认输出格式)占位符来打印输入,它非常适用于展示数组。
- 先使用最少的代码来让失败的测试先跑起来
package arrays
func Sum(numbers [5]int) (sum int) {
return 0
}
//func Sum(numbers [5]int) (sum int) {
// for i := 0; i < 5; i++ {
// sum += numbers[i]
// }
// return sum
//}
//func Sum(numbers [5]int) (sum int) {
// for _, number := range numbers {
// sum += number
// }
// return sum
//}
- 数组和它的类型
数组有一个有趣的属性,它的大小也属于类型的一部分,如果你尝试将 [4]int
作为 [5]int
类型的参数传入函数,是不能通过编译的。它们是不同的类型,就像尝试将 string
当做 int
类型的参数传入函数一样。
因为这个原因,所以数组比较笨重,大多数情况下我们都不会使用它。
Go 的切片(slice)类型不会将集合的长度保存在类型中,因此它的尺寸可以是不固定的。
下面我们会完成一个动态长度的 Sum
函数。
func Sum(numbers []int) (sum int) {
for _, number := range numbers {
sum += number
}
return sum
}
我们可以使用 range
语法来让函数变得更加整洁。range
会迭代数组,每次迭代都会返回数组元素的索引和值。我们选择使用 _
空白标志符 来忽略索引。
- 重构
我们已经重构了 Sum 函数把参数从数组改为切片。注意不要在重构以后忘记维护你的测试代码。
func TestSum(t *testing.T) {
t.Run("collection of 5 numbers", func(t *testing.T) {
numbers := []int{1, 2, 3, 4, 5}
got := Sum(numbers)
want := 15
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
t.Run("collection of any size", func(t *testing.T) {
numbers := []int{1, 2, 3}
got := Sum(numbers)
want := 6
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
}
Go 有内置的计算测试 覆盖率的工具,它能帮助你发现没有被测试过的区域。我们不需要追求 100% 的测试覆盖率,它只是一个供你获取测试覆盖率的方式。只要你严格遵循 TDD 规范,那你的测试覆盖率就会很接近 100%。
运行:
go test -cover
- 另一个例子
这回我们需要一个 SumAll 函数,它接受多个切片,并返回由每个切片元素的总和组成的新切片。
例如:SumAll([]int{1,2}, []int{0,9}) 返回 []int{3, 9}
或者: SumAll([]int{1,1,1}) 返回 []int{3}
先编写测试
func TestSumAll(t *testing.T) {
got := SumAll([]int{1,2}, []int{0,9})
want := []int{3, 9}
if got != want {
t.Errorf("got %v want %v", got, want)
}