测试
单元测试
- 文件 _test 结尾,方法名 Test 开头
- Fail,Error: 测试失败,该测试继续,其他测试也继续执行
- FailNow, Fatal: 该测试失败,该测试终止,其他测试继续执行
代码覆盖率
go test -v -cover
断言:
https://github.com/stretchr/testify
func TestForSqure(t *testing.T) {
inputs := [...]int{1, 2, 3}
expected := [...]int{1, 4, 9}
for i := 0; i < len(inputs); i++ {
ret := square(inputs[i])
if ret != expected[i] {
t.Errorf("input %d expected %d, the actual is %d",
inputs[i], expected[i], ret)
}
}
}
func square(i int) int {
return i * i
}
func TestErrorInCode(t *testing.T) {
fmt.Println("Start")
t.Error("Error")
fmt.Println("End")
}
func TestFailInCode(t *testing.T) {
fmt.Println("Start")
t.Fatal("Error")
fmt.Println("End")
}
BenchNow 性能测评
运行所有文件 go test -bench=. [-benchmenm (分析内存大小,allocs 内存个数)]
运行方法 go test -bench=方法名 [-benchmenm (分析内存大小,allocs 内存个数)]
func BenchmarkXX(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
}
b.StopTimer()
}
BDD (Behavior Driven Developement)
BDD in Go
地址: https://github.com/smartystreets/goconvey
安装:
go get -u github.com/smartystreets/goconvey
启动 Web ui:
$GOPATH/bin/goconvey
反射 基础操作
反射
反射类型 reflect.TypeOf
反射值 reflect.ValueOf
- reflect.TypeOf 返回类型(reflect.Type)
- reflect.ValueOf 返回值 (reflect.Value)
- 可以从 reflect.Value 获得类型
- 通过 kind 来判断类型(枚举)
func CheckType(v interface{}) {
t := reflect.TypeOf(v)
switch t.Kind() {
case reflect.Float32, reflect.Float64:
fmt.Println("Float")
case reflect.Int, reflect.Int32, reflect.Int64:
fmt.Println("Integer")
case reflect.Ptr:
fmt.Println("Ptr")
default:
fmt.Println("other type")
}
}
func TestBaseType(t *testing.T) {
var f float64 = 1
CheckType(f)
CheckType(&f)
}
func TestTypeAndValue(t *testing.T) {
var f int64 = 10
t.Log(reflect.TypeOf(f), reflect.ValueOf(f))
t.Log(reflect.ValueOf(f).Type())
}
通过字符串的形式调用类型的某个方法
按名字访问结构的成员
reflect.ValueOf(*e).FieldByName("Name")
按名字访问结构的方法
reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(1)})
type EmployeeT struct {
EmployeeId string
Name string `format:"normal"`
Age int
}
func (e *EmployeeT) UpdateAge(newVal int) {
e.Age = newVal
}
func TestInvokeByName(t *testing.T) {
e := &EmployeeT{"1", "Mike", 30}
t.Logf("Name:value(%[1]v), Type(%[1]T)",
reflect.ValueOf(*e).FieldByName("EmployeeId"))
if nameField, ok := reflect.TypeOf(*e).FieldByName("Name"); !ok {
t.Error("Failed to get 'name' field")
} else {
t.Log("Tag: format", nameField.Tag.Get("format"))
}
reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(99)})
t.Log("update age:", e)
}
反射 万能程序
主要列举几个应用反射的例子。
DeepEqual
DeepEqual 比较切片和 map
func TestDeepEqual(t *testing.T) {
a := map[int]string{1: "one", 2: "two", 3: "three"}
b := map[int]string{1: "two", 2: "two", 4: "three"}
fmt.Println(reflect.DeepEqual(a, b))
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
s3 := []int{1, 2, 4}
t.Log("s1 == s2 ? ", reflect.DeepEqual(s1, s2))
t.Log("s1 == s3 ? ", reflect.DeepEqual(s1, s3))
}
结构体填充
type Customer struct {
CookieID string
Name string
Age int
}
func fillBySettings(st interface{}, settings map[string]interface{}) error {
if reflect.TypeOf(st).Kind() != reflect.Ptr {
return errors.New("the first param should be a pointer to the struct type.")
}
if reflect.TypeOf(st).Elem().Kind() != reflect.Struct {
return errors.New("the first param should be a pointer to the struct type.")
}
if settings == nil {
return errors.New("settings is nil.")
}
var (
field reflect.StructField
ok bool
)
for k, v := range settings {
if field, ok = (reflect.ValueOf(st)).Elem().Type().FieldByName(k); !ok {
continue
}
if field.Type == reflect.TypeOf(v) {
vstr := reflect.ValueOf(st)
vstr = vstr.Elem()
vstr.FieldByName(k).Set(reflect.ValueOf(v))
}
}
return nil
}
func TestFillNameAndAge(t *testing.T) {
settings := map[string]interface{}{"Name": "Mike", "Age": 30}
e := Employee{}
if err := fillBySettings(&e, settings); err != nil {
t.Fatal(err)
}
t.Log(e)
c := new(Customer)
if err := fillBySettings(c, settings); err != nil {
t.Fatal(err)
}
t.Log(*c)
}
反射 unsafe poiner
不安全编程的危险性
指针可以转换成任意类型的指针,利用它来实现类型转换. 非常危险
i := 10
f := *(float64)(unsafe.Pointer(&i))
func TestUnsafe(t *testing.T) {
i := 10
f := *(*float64)(unsafe.Pointer(&i))
t.Log(unsafe.Pointer(&i))
t.Log(f)
}
atomic 包 进行指针的读写操作
func TestAtomic(t *testing.T) {
var shareBufPtr unsafe.Pointer
writeDataFn := func() {
data := []int{}
for i := 0; i < 100; i++ {
data = append(data, i)
}
atomic.StorePointer(&shareBufPtr, unsafe.Pointer(&data))
}
readDataFn := func() {
data := atomic.LoadPointer(&shareBufPtr)
fmt.Println(data, *(*[]int)(data))
}
var wg sync.WaitGroup
writeDataFn()
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
for i := 0; i < 10; i++ {
writeDataFn()
time.Sleep(time.Microsecond * 100)
}
wg.Done()
}()
wg.Add(1)
go func() {
for i := 0; i < 10; i++ {
readDataFn()
time.Sleep(time.Microsecond * 100)
}
wg.Done()
}()
}
wg.Wait()
}
架构
Pip-Filter 架构
适合数据处理,数据分析
相当于一个个的 filter,对数据进行处理。
- 非常适合与数据处理及数据分析系统
- Filter 疯转数据处理的功能
- 松耦合:Filter 只和数据(格式)耦合
- Pipe 用于连接 Filter 传递数据或者在异步处理过程中缓冲数据流
- 进程内同步调用时,pipe 演变为数据在方法调用间传递
代码示例
源码文件见 quicktouch/go-learn
架构模式 micro kernel
特点:
要点
- 内核包含公共流程或通用逻辑
- 将可变或可扩展部门规划为扩展点
- 抽象扩展行为,定义接口
- 利用插件进行扩展
json 解析
内置的 json 解析
- 利用反射实现,通过 FieldTag 来标识对应的 json 值
因为利用了反射,性能不行,一般可用于配置文件的解析
type BasicInfo struct {
Name string `json:"name"`
Age int `json:"age"`
}
type JobInfo struct {
Skills []string `json:"skills"`
}
type EmployeeStruct struct {
BasicInfo BasicInfo `json:"basic_info"`
JobInfo JobInfo `json:"job_info"`
}
var jsonStr = `{
"basic_info": {
"name":"Mike",
"age":30
},
"job_info": {
"skills":["Java","Go","C"]
}
}`
func TestEmbeddedJson(t *testing.T) {
e := new(EmployeeStruct)
err := json.Unmarshal([]byte(jsonStr), e)
if err != nil {
t.Error(err)
}
fmt.Println(*e)
if v, err := json.Marshal(e); err == nil {
fmt.Println(string(v))
} else {
t.Error(err)
}
}
EasyJson
采用代码生成而非反射.
安装: go get -u github.com/mailru/easyjson/...
使用 EasyJson 生成对应的解析方法文件(struct 文件需要在 gopath 下):
~/go/bin/easyjson -all /path/to/fileName.go
var jsonStr = `{
"basic_info": {
"name":"Mike",
"age":30
},
"job_info": {
"skills":["Java","Go","C"]
}
}`
func TestEasyJson(t *testing.T) {
e := EmployeeStruct{}
err := e.UnmarshalJSON([]byte(jsonStr))
if err != nil {
t.Error(err)
} else {
fmt.Println(e)
}
if v, err := e.MarshalJSON(); err != nil {
t.Error(err)
} else {
fmt.Println(string(v))
}
}
性能测试
使用go test -bench=.
进行性能测试
func BenchmarkEasyJson(b *testing.B) {
b.ResetTimer()
e := EmployeeStruct{}
for i := 0; i < b.N; i++ {
err := e.UnmarshalJSON([]byte(jsonStr))
if err != nil {
b.Error(err)
}
if _, err := e.MarshalJSON(); err != nil {
b.Error(err)
}
}
}
func BenchmarkEmbedJson(b *testing.B) {
b.ResetTimer()
e := new(EmployeeStruct)
for i := 0; i < b.N; i++ {
err := json.Unmarshal([]byte(jsonStr), e)
if err != nil {
b.Error(err)
}
if _, err := json.Marshal(e); err != nil {
b.Error(err)
}
}
}
http 示例
自带的 http
url 匹配:
- URL 分为两种,末尾是
/:
表示一个子树,后面可以跟其他子路径;末尾不是/
,表示一个叶子,固定的路径。
- 以
/
结尾的 url 可以匹配它的任何子路径,比如 /images 会匹配到 /images/1.jpg
- 采用最长匹配原则,如果有多个匹配,一定采用匹配路径最长的那个进行处理
- 如果没有找到任何匹配项,会返回 404 错误
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello world")
})
http.HandleFunc("/time/", func(w http.ResponseWriter, r *http.Request) {
t := time.Now()
timeStr := fmt.Sprintf("{\"time\":\"%s\"}", t)
_, _ = w.Write([]byte(timeStr))
})
_ = http.ListenAndServe(":8080", nil)
}
第三方框架
https://github.com/julienschmidt/httprouter
更简单,也方便构建 RESTful
go get -u github.com/julienschmidt/httprouter
示例:
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "welcome!")
}
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hello, %s", ps.ByName("name"))
}
func main() {
router := httprouter.New()
router.GET("/", Index)
router.GET("/hello/:name", Hello)
log.Fatal(http.ListenAndServe(":8080", router))
}
性能分析 工具
相关依赖安装
安装 graphviz 添加图形的支持
brew install graphviz
安装 go-torch
- go get github.com/uber/go-torch
- git clone https://github.com/brendangregg/FlameGraph.git 将 flamegraph.pl 拷贝到
/usr/local/bin
目录下
- 输入
flamegraph.pl -h
可以看到是否成功
通过文件方式输出 Profile
可以输出 cpu 内存 线程数等。
相关文档查看:
编译测试代码:
查看这些文件 (适合短时间运行的程序)
go tool pprof 编译出的二进制 cpu.prof/goroutine.prof/mem.prof
例如:
go tool pprof prof cpu.prof
File: prof
Type: cpu
Time: Feb 2, 2020 at 10:44pm (CST)
Duration: 2.06s, Total samples = 1.81s (87.91%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 1.81s, 100% of 1.81s total
Showing top 10 nodes out of 12
flat flat% sum% cum cum%
1.77s 97.79% 97.79% 1.78s 98.34% main.fillMatrix
0.02s 1.10% 98.90% 0.02s 1.10% main.calculate
0.01s 0.55% 99.45% 0.01s 0.55% math/rand.(*rngSource).Int63
0.01s 0.55% 100% 0.01s 0.55% runtime.newstack
0 0% 100% 1.81s 100% main.main
0 0% 100% 0.01s 0.55% math/rand.(*Rand).Int31
0 0% 100% 0.01s 0.55% math/rand.(*Rand).Int31n
0 0% 100% 0.01s 0.55% math/rand.(*Rand).Int63
0 0% 100% 0.01s 0.55% math/rand.(*Rand).Intn
0 0% 100% 0.01s 0.55% os.Create
(pprof) list fillMatrix
Total: 1.81s
ROUTINE ======================== main.fillMatrix in /Users/panda/Desktop/go-learn/playground/src/lession/tools/file/prof.go
1.77s 1.78s (flat, cum) 98.34% of Total
. . 15:
. . 16:func fillMatrix(m *[row][col]int) {
. . 17: s := rand.New(rand.NewSource(time.Now().UnixNano()))
. . 18: for i := 0; i < row; i++ {
. . 19: for j := 0; j < col; j++ {
1.77s 1.78s 20: m[i][j] = s.Intn(100000)
. . 21: }
. . 22: }
. . 23:}
. . 24:
. . 25:func calculate(m *[row][col]int) {
(pprof) exit
go-torch
go-torch 查看火炬图。
以 http 的方式输出 profile
- 简单,适合持续性运行的程序
- 应用程序中导入
import _ "net/http/pprof"
, 并启动 http server 即可
- http://:/debug/pprof/
- go tool pprof http://:/debug/pprof/profile?seconds-10 (默认 30s)
- go-torch -seconds 10 http://:/debug/pprof/profile
import (
"fmt"
"net/http"
_ "net/http/pprof"
)
func GetFibonacciSeries(n int) ([]int, error) {
fibList := []int{1, 1}
for i := 2; i < n; i++ {
fibList = append(fibList, fibList[i-2]+fibList[i-1])
}
return fibList, nil
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("welcome!"))
})
http.HandleFunc("/fb", func(w http.ResponseWriter, r *http.Request) {
var fabs []int
for i := 0; i < 100000; i++ {
fabs, _ = GetFibonacciSeries(50)
}
w.Write([]byte(fmt.Sprintf("%v", fabs)))
})
_ = http.ListenAndServe(":8080", nil)
}