单元测试
代码 main.go
package main
import (
"strings"
)
/*标题: 单元测试和性能基准测试*/
// 待测试的字符串切割函数
func Split(str string, sep string) []string {
var ret []string
index := strings.Index(str, sep)
for index >= 0 {
ret = append(ret, str[:index])
str = str[index+1:]
index = strings.Index(str, sep)
}
ret = append(ret, str[:])
return ret
}
func main() {}
代码 main_test.go
(单元测试的文件名必须是原文件名+_test)
package main
import (
"reflect"
"testing"
)
func TestSplit(t *testing.T) {
got := Split("a:b:c", ":")
want := []string{"a", "b", "c"}
if !reflect.DeepEqual(got, want) {
t.Errorf("want:%v but got:%v", want, got)
}
}
func Test2Split(t *testing.T) {
got := Split("a::b::c", "::")
want := []string{"a", "b", "c"}
if !reflect.DeepEqual(got, want) {
t.Fatalf("want:%v but got:%v", want, got)
}
}
// 子测试
// 通过 go test -run=Test3Split/case_2 可以运行一个子测试用例
func Test3Split(t *testing.T) {
type testCase struct {
str string
sep string
want []string
}
testGroup := map[string]testCase{
"case_1": {
str: "a:b:c",
sep: ":",
want: []string{"a", "b", "c"},
},
"case_2": {
str: "a::b::c",
sep: "::",
want: []string{"a", "b", "c"},
},
}
for name, c := range testGroup {
t.Run(name, func(t *testing.T) {
got := Split(c.str, c.sep)
if !reflect.DeepEqual(got, c.want) {
t.Fatalf("want:%v but got:%v", c.want, got)
}
})
}
}
// 测试覆盖率
// go test -cover
// 将覆盖率输出到文件
// go test -cover -coverprofile=cover.out
// 用网页查看输出的覆盖率
// go tool cover -html=cover.out
//
// idea 可以编辑测试的属性, 在Go tool augment中增加-cover选项
性能测试
代码 main_test.go
package main
import "testing"
func BenchmarkSplit(b *testing.B) {
// 在这里做一些不需要被记入时间的操作, 然后
b.ResetTimer() // 重置计时器
for i := 0; i < b.N; i++ {
Split("a:b:c", ":")
}
}
// benchmark需要用 go test -bench=Split执行
// 输出结果
// goos: windows
// goarch: amd64
// pkg: github.com/go-learn/chp1
// BenchmarkSplit
// BenchmarkSplit-4 1331119 840 ns/op // 数字4表示GOMAXPROCS
// PASS
// 使用 go test -bench=Split -benchmem 查看内存操作情况
// 并行测试
func BenchmarkSplitParallel(b *testing.B) {
b.SetParallelism(1) // 设置使用的CPU数
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Split("a:b:c", ":")
}
})
}
性能日志
package main
import (
"flag"
"fmt"
"os"
"runtime/pprof"
"time"
)
func argsTest() {
// 运行 main.exe a b c
fmt.Println(os.Args) // 输出 [main.exe a b c]
}
func flagTest() {
// 运行 main.exe -name=刘备
// 先定义有哪些参数
name := flag.String("name", "张飞", "名字") // 参数是 name, default_value, usage
when := flag.Duration("time", time.Second, "时刻") // 用户需要按照 time.ParseDuration() 定一个格式输入时间
// 定义和取值同时发生
var gender bool
flag.BoolVar(&gender, "male", true, "男=true 女=false")
flag.Parse()
fmt.Println(*name) // 输出 刘备
fmt.Println(when)
fmt.Println(gender)
// 输出其他参数
fmt.Println(flag.Args()) // 返回命令行参数后的其他参数
fmt.Println(flag.NArg()) // 返回flag.Args()的元素个数
fmt.Println(flag.NFlag()) // 返回使用的命令行参数的个数
}
// 一段有问题的代码
func logicCode() {
var c chan int
for {
select {
case v := <-c: // 阻塞(不执行)
fmt.Printf("recv from chan, value:%v\n", v)
default: // 死循环
time.Sleep(time.Millisecond * 10)
}
}
}
func main() {
var isCPUPprof bool
var isMemPprof bool
flag.BoolVar(&isCPUPprof, "cpu", false, "turn cpu pprof on")
flag.BoolVar(&isMemPprof, "mem", false, "turn mem pprof on")
flag.Parse()
if isCPUPprof {
file, err := os.Create("./cpu.pprof")
if err != nil {
fmt.Println("create cpu pprof failed, err:", err)
return
}
pprof.StartCPUProfile(file)
defer file.Close()
defer pprof.StopCPUProfile()
}
for i := 0; i < 6; i++ {
go logicCode()
}
time.Sleep(20 * time.Second)
if isMemPprof {
file, err := os.Create("./mem.pprof")
if err != nil {
fmt.Println("create mem pprof failed, err:", err)
return
}
pprof.WriteHeapProfile(file)
defer file.Close()
}
}
// 然后用 go tool pprof cpu.pprof 进入pprof的交互模式
// 输入top显示所有函数的时间情况
// 输入list logicCode查看logicCode的时间占用情况
// 也可以用 graphviz 工具查看 pprof 文件, 下载地址 http://www.graphviz.org/download/
// 也可以使用 go-torch工具 查看火焰图