1.单元测试初步讲解
Go 语言推荐测试文件和源代码文件放在同一目录下,测试文件以 _test.go 结尾。比如,当前 package 有 calc.go 一个文件,我们想测试 calc.go 中的 Add 和 Mul 函数,那么应该新建 calc_test.go 作为测试文件
那么在Goland IDE中,我们一般采用如下方法做单元测试:
首先,单元测试的目录结构:
我们选中两个目标(源代码+单元测试test),右键做如下选择:
之后将会生成单元测试的结果
2.并发
协程 Goroutine
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
goroutine 语法格式:
go 函数名( 参数列表 )
协程演示:
package concurrence import ( "fmt" "time" ) // 函数 func hello(i int) { println("hello goroutine : " + fmt.Sprint(i)) } func HelloGoRoutine() { for i := 0; i < 5; i++ { // 开启一个新的协程 go func(j int) { hello(j) }(i) } // 需要等待片刻 time.Sleep(time.Second) }
单元测试:
package concurrence import ( "testing" ) func TestManyGo(t *testing.T) { HelloGoRoutine() }
输出:他们是两个进程在工作,显然是没有规律的:
hello goroutine : 4 hello goroutine : 3 hello goroutine : 0 hello goroutine : 2 hello goroutine : 1
通道 Channel
通道(channel
)是用来传递数据的一个数据结构。
通道可用于两个 goroutine
之间通过传递一个指定类型的值来同步运行和通讯。操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
声明一个通道很简单,我们使用chan
关键字即可,通道在使用前必须先创建:
ch := make(chan int)
通道可以设置缓冲区,通过 make
的第二个参数指定缓冲区大小
ch := make(chan int, 100)
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
通道演示,计算0–9的平方:
package concurrence func CalSquare() { // 声明两个通道 src := make(chan int) // 声明一个带缓冲区的通道 dest := make(chan int, 3) go func() { defer close(src) for i := 0; i < 10; i++ { // 将i一个一个传到src通道里面 src <- i } }() go func() { defer close(dest) // 一个一个接受src通道里的值,进入dest通道 for i := range src { // 在dest通道中,计算i的平方 dest <- i * i } }() // 输出dest通道里的所有值 for i := range dest { //复杂操作 println(i) } }
单元测试:
package concurrence import "testing" func TestCalSquare(t *testing.T) { CalSquare() }
测试输出:
0 1 4 9 16 25 36 49 64 81 --------------------- 可以看到,是正确的输出结果
锁Lock和线程同步 WaitGroup
介绍了锁及线程同步的使用场景和方法:
package concurrence import ( "sync" "time" ) var ( x int64 lock sync.Mutex ) func addWithLock() { for i := 0; i < 2000; i++ { lock.Lock() x += 1 lock.Unlock() } } func addWithoutLock() { for i := 0; i < 2000; i++ { x += 1 } } func Add() { x = 0 for i := 0; i < 5; i++ { go addWithoutLock() } time.Sleep(time.Second) println("WithoutLock:", x) x = 0 for i := 0; i < 5; i++ { go addWithLock() } time.Sleep(time.Second) println("WithLock:", x) } func ManyGoWait() { var wg sync.WaitGroup wg.Add(5) for i := 0; i < 5; i++ { go func(j int) { defer wg.Done() // 这里的hello是goroutine.go里的函数 hello(j) }(i) } wg.Wait() }
单元测试:
package concurrence import "testing" func TestAddLock(t *testing.T) { Add() } func TestManyGoWait(t *testing.T){ ManyGoWait() }
3.单元测试细讲
例子A
业务代码:主要的作用是返回一个字符串:
package test func HelloTom() string { return "Tom" }
单元测试,检测返回结果是否正确:
package test import ( "github.com/stretchr/testify/assert" "testing" ) func TestHelloTom(t *testing.T) { output := HelloTom() expectOutput := "Tom" assert.Equal(t, expectOutput, output) }
例子B
业务代码,主要是判断分数是否及格:
package test func JudgePassLine(score int16) bool { if score >= 60 { return true } return false }
单元测试:测试及格和不及格的预想输出:
package test import ( "github.com/stretchr/testify/assert" "testing" ) func TestJudgePassLineTrue(t *testing.T) { isPass := JudgePassLine(70) assert.Equal(t, true, isPass) } func TestJudgePassLineFail(t *testing.T) { isPass := JudgePassLine(50) assert.Equal(t, false, isPass) }
Mock 测试
业务代码,对文件内容的一些操作:
package test import ( "bufio" "os" "strings" ) // 读文件内容 func ReadFirstLine() string { // 打开目录下的log文件 open, err := os.Open("log") defer open.Close() if err != nil { return "" } // 读取文件,并返回 scanner := bufio.NewScanner(open) for scanner.Scan() { return scanner.Text() } return "" } // 选出文件的首行内容 func ProcessFirstLine() string { line := ReadFirstLine() // 将文件中的11字符替换为00字符 destLine := strings.ReplaceAll(line, "11", "00") return destLine }
单元测试:
package test import ( "bou.ke/monkey" "github.com/stretchr/testify/assert" "testing" ) func TestProcessFirstLine(t *testing.T) { firstLine := ProcessFirstLine() // 看一下是否成功替换了 assert.Equal(t, "line00", firstLine) } func TestProcessFirstLineWithMock(t *testing.T) { monkey.Patch(ReadFirstLine, func() string { return "line110" }) defer monkey.Unpatch(ReadFirstLine) line := ProcessFirstLine() assert.Equal(t, "line000", line) }
基准测试
业务代码:随机数:
package benchmark import ( "github.com/bytedance/gopkg/lang/fastrand" "math/rand" ) var ServerIndex [10]int func InitServerIndex() { for i := 0; i < 10; i++ { ServerIndex[i] = i+100 } } func Select() int { return ServerIndex[rand.Intn(10)] } func FastSelect() int { return ServerIndex[fastrand.Intn(10)] }
基准测试,单元测试:
package benchmark import ( "testing" ) func BenchmarkSelect(b *testing.B) { InitServerIndex() b.ResetTimer() for i := 0; i < b.N; i++ { Select() } } func BenchmarkSelectParallel(b *testing.B) { InitServerIndex() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { Select() } }) } func BenchmarkFastSelectParallel(b *testing.B) { InitServerIndex() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { FastSelect() } }) }