测试

单元测试

  1. 文件 _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)  // Float
	CheckType(&f) // Ptr
}

func TestTypeAndValue(t *testing.T) {
	var f int64 = 10
	t.Log(reflect.TypeOf(f), reflect.ValueOf(f)) // int64 10
	t.Log(reflect.ValueOf(f).Type())             //int64
}

通过字符串的形式调用类型的某个方法

按名字访问结构的成员
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"` //struct tag ,类似java的注解
	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")) //  Name:value(1), Type(reflect.Value)

	if nameField, ok := reflect.TypeOf(*e).FieldByName("Name"); !ok {
		t.Error("Failed to get 'name' field")
	} else {
		// 通过反射获取struct tag
		t.Log("Tag: format", nameField.Tag.Get("format")) // Tag: format normal
	}

	reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(99)})
	t.Log("update age:", e) // update age: &{1 Mike 99}   age更新为99
}

反射 万能程序

主要列举几个应用反射的例子。

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(a == b) // 编译不通过
	fmt.Println(reflect.DeepEqual(a, b)) //false

	s1 := []int{1, 2, 3}
	s2 := []int{1, 2, 3}
	s3 := []int{1, 2, 4}
	t.Log("s1 == s2 ? ", reflect.DeepEqual(s1, s2)) // true
	t.Log("s1 == s3 ? ", reflect.DeepEqual(s1, s3)) // false

}

结构体填充

type Customer struct {
	CookieID string
	Name     string
	Age      int
}

/*
 结构体填充
*/
func fillBySettings(st interface{}, settings map[string]interface{}) error {

	// func (v Value) Elem() Value
	// Elem returns the value that the interface v contains or that the pointer v points to.
	// It panics if v's Kind is not Interface or Ptr.
	// It returns the zero Value if v is nil.

	if reflect.TypeOf(st).Kind() != reflect.Ptr {
		return errors.New("the first param should be a pointer to the struct type.")
	}
	// Elem() 获取指针指向的值
	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 {
		// 检查类型里有没有field   (因为是指针类型,需要用Elem(),获取指针类型的)
		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)) //  0xc000016298
	t.Log(f)                  // 5e-323
}

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架构

适合数据处理,数据分析

20200131158048235954296.png

相当于一个个的filter,对数据进行处理。

  • 非常适合与数据处理及数据分析系统
  • Filter疯转数据处理的功能
  • 松耦合:Filter只和数据(格式)耦合
  • Pipe用于连接Filter传递数据或者在异步处理过程中缓冲数据流
    • 进程内同步调用时,pipe演变为数据在方法调用间传递

20200131158048264844481.png 20200131158048265315848.png

代码示例

源码文件见 quicktouch/go-learn

架构模式 micro kernel

2020020115805305425749.png

特点:

  • 易于扩展
  • 错误隔离
  • 保持架构一致性

要点

  • 内核包含公共流程或通用逻辑
  • 将可变或可扩展部门规划为扩展点
  • 抽象扩展行为,定义接口
  • 利用插件进行扩展

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)
	// json 赋值给struct
	err := json.Unmarshal([]byte(jsonStr), e)
	if err != nil {
		t.Error(err) // {{Mike 30} {[Java Go C]}}
	}
	fmt.Println(*e)
	// struct反序列化
	if v, err := json.Marshal(e); err == nil {
		fmt.Println(string(v))
		// {"basic_info":{"name":"Mike","age":30},"job_info":{"skills":["Java","Go","C"]}}
	} 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) // {{Mike 30} {[Java Go C]}}
	}

	if v, err := e.MarshalJSON(); err != nil {
		t.Error(err)
	} else {
		fmt.Println(string(v)) // {"basic_info":{"name":"Mike","age":30},"job_info":{"skills":["Java","Go","C"]}}
	}
}

性能测试

使用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++ {
		// json 赋值给struct
		err := json.Unmarshal([]byte(jsonStr), e)
		if err != nil {
			b.Error(err)
		}
		if _, err := json.Marshal(e); err != nil {
			b.Error(err)
		}
	}
}

// go test -bench=.

//goos: darwin
//goarch: amd64
//BenchmarkEasyJson-8      1262138 (运行次数)              936 ns/op (每次运行的时间/纳秒)
//BenchmarkEmbedJson-8      334260                       3494 ns/op

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") // 返回给客户端
	})
	// 可以匹配 time/*
	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))
}

// http://localhost:8080/hello/google ->  hello, google

性能分析 工具

相关依赖安装

安装graphviz 添加图形的支持

brew install graphviz

安装go-torch

  1. go get github.com/uber/go-torch
  2. git clone https://github.com/brendangregg/FlameGraph.git 将 flamegraph.pl 拷贝到/usr/local/bin目录下
  3. 输入 flamegraph.pl -h 可以看到是否成功

通过文件方式输出 Profile

可以输出cpu 内存 线程数等。

相关文档查看:

编译测试代码:

# 编译测试代码
go build prof.go
# 运行二进制, 运行完多了 cpu.prof goroutine.prof mem.prof 这几个文件
./prof

查看这些文件 (适合短时间运行的程序)

go tool pprof 编译出的二进制 cpu.prof/goroutine.prof/mem.prof

例如:

go tool pprof prof cpu.prof

# log 如下,
# 随后使用top命令可以看到cpu详情。  flat表示所占时间和所占比例。 cum/cum%表示这个函数还调用了别的函数,总体加和在一起所占的时间和比例。
# 使用list + 符号名 命令可看具体哪行的耗时
# svg 以图像的方式查看调用关系及具体的cpu耗时,比较直观。
# exit 退出

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查看火炬图。

go-torch cpu.prof

以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)
}

/*

访问 http://localhost:8080/debug/pprof/  可看到一些分析

点击profile会进行30秒的采样,并下载文件。

也能用命令行

go tool pprof http://<host>:<port>/debug/pprof/profile?seconds=10

然后使用 top命令  (top排序,如按cum排序: top -cum)
命令: list 方法名
等。

 */

Go语言学习笔记(二) WebViewJavascriptBridge源码分析