测试
单元测试benchmarkBDD 反射
万能程序 不安全编程
测试结束了核心的协程部分,这部分开始讲go的测试 单元测试
这个我们很熟悉了,常用的是t.Log t.Error,这里有点区别
package mytest import "testing" import "fmt" func TestError(t *testing.T) { fmt.Println("start test") t.Error("fatal error") fmt.Println("end test") // end test } func TestFatal(t *testing.T) { fmt.Println("start test") t.Fatal("fatal error") fmt.Println("end test") // 不会执行 }也可以下载断言测试包
package mytest import "testing" import "fmt" import "github.com/stretchr/testify/assert" func Square(input int) int { return input * input } func TestAssert(t *testing.T) { inputs := [...]int{1,2,3} expected := [...]int{1,4,9} for i:=0; i如果想在命令行中得到测试覆盖率:go test -v -cover 会执行该目录下的全部测试文件并给出coverage类似的可以使用go build -race 来进行数据竞争检测,看是否加锁 benchmark 一般是看哪个库更好用做一个测评,或者代码怎么写性能更好做测试和Test类似,以_test.go结尾文件,看个字符串拼接的例子
package mybench import "testing" import "bytes" func BenchmarkConcatStringByAdd(b *testing.B) { elements := []string{"1", "2", "3", "4"} b.ResetTimer() // 假装运行别的代码呢 b.Log(b.N) // 这是个随机值 for i:=0; i为什么用buffer更快呢?用-benchmem参数便知:
buffer只分配了一次内存,而+拼接每拼一个就要分配新空间!之前的泄露 后面分析代码性能的时候会经常用到 BDD
behavior driven development为降低开发人员和验收人员之间的沟通成本,推出了很多BDD的测试框架例如:goconvey
// 安装:go get github.com/smartystreets/goconvey/convey // git config --global --unset http.proxy // git config --global --unset https.proxy package mybdd import "testing" import . "github.com/smartystreets/goconvey/convey" // 使用 . 做别名可以直接使用其方法 func TestBDD(t *testing.T) { Convey("given two even numbers", t, func() { // 只有最外层的Convey需要传入*testing.T类型的变量t a := 2 b := 4 Convey("when add two numbers", func() { // 第三个参数习惯以闭包的形式实现 c := a+b Convey("the result is still even", func() { So(c%2, ShouldEqual, 0) // 断言 }) }) }) }我们看结果:=== RUN TestBDD given two even numbers when add two numbers the result is still even . 1 total assertion --- PASS: TestBDD (0.00s)这个测试框架从输入、到代码大致流程、到最后的结果都呈现出来,对开发人员和客户(测试)都比较友好还提供了web界面:$GOPATH/bin/goconvey 很多包的功能不能使用的问题
在bin下没有可执行文件
Go的包分为两部分,一是GOROOT(安装目录)下自带的包,包括testing等;二是GOPATH(工作目录)下,自己go get的包都会到这运行时保存可以根据提示go get -u那个包 更新Go版本:直接下载新版本安装覆盖(还是先全部卸载吧)修改 GO111MODULE 的值的语句是:go env -w GO111MODULE=auto,这个是官方包管理工具,默认不设置
off,则到GOPATH(工作目录)定位包on,使用go module管理包,执行文件所在包(文件夹)需要有go.mod文件auto,哪个好用就用哪个 反射
很多编程语言都支持反射
反射出类型:reflect.Type反射出值:reflect.ValueOf
package myreflect import "testing" import "reflect" import "fmt" func CheckType(v interface{}) { t := reflect.TypeOf(v) // 类型 fmt.Println(t) // .Kind() switch t.Kind() { // 判断类型 case reflect.Float32, reflect.Float64: fmt.Println("float") case reflect.Int, reflect.Int32, reflect.Int64: fmt.Println("int") default: fmt.Println("啥玩意?",t) } } func TestCheckType(t *testing.T) { var value float64 CheckType(&value) // 实参 & } func TestTypeAndValue(t *testing.T) { var f int = 10 t.Log(reflect.TypeOf(f), reflect.ValueOf(f)) // int 10 t.Log(reflect.ValueOf(f).Type()) // int }使用反射可以写更灵活的代码,比如Java的很多框架都是依托反射的原理实现的反射出类模型,访问属性和方法
看个自定义类的例子:package myreflect import "testing" import "reflect" type Empolyee struct { EmpolyeeID string Name string `format:"normal"` // k-v 结构的标记,后面学json解析要用到 Age int } func (e *Empolyee) UpdateAge(newAge int) { e.Age = newAge } type Customer struct { cookieID string Name string Age int } func TestInvokeByName(t *testing.T) { // invoke 调用 e := &Empolyee{"1", "Roy", 18} // 赋值给 e t.Logf("Name: %[1]v, Type: %[1]T ", reflect.ValueOf(*e).FieldByName("Name")) // Name: Roy, Type: reflect.Value %[1] 都用第一个参数 if name, ok := reflect.TypeOf(*e).FieldByName("Name"); !ok { // Type和Value都有此方法,注意区别 t.Error("fail to get 'Name' field") }else { t.Logf("Name: %v", name) // Name: {Name string format:"normal" 16 [1] false} t.Log("Tag:", name.Tag.Get("format")) // Tag: normal } reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(2)})// 得到方法, 并调用;这个传参怎么理解 t.Log("Update Age:", e) // Update Age: &{1 Roy 1} }万能程序之前说过map只能和nil比较,用逻辑运算符但是reflect还提供了一种可以比较的方法:DeepEqual()
package myreflect import "testing" import "reflect" func TestDeepEqual(t *testing.T) { a := map[int]string{1:"one", 2:"two"} b := map[int]string{1:"one", 2:"two"} // t.Log(a==b) // invalid operation: a == b (map can only be compared to nil) t.Log(reflect.DeepEqual(a,b)) // true s1 := []int{1,2,3} s2 := []int{1,2,3} s3 := []int{3,4,5} t.Log("s1==s2?", reflect.DeepEqual(s1,s2)) // true t.Log("s1==s3?", reflect.DeepEqual(s1,s3)) // false }使用反射是追求灵活,为了使程序也能更简便,可以自定义一个万能程序,初始化对象package myreflect import "testing" import "reflect" import "fmt" import "errors" // 万能程序 func fillBySettings(st interface{}, settings map[string]interface{}) error { if reflect.TypeOf(st).Kind() != reflect.Ptr { // 如果传入的不是对象指针 // 实例化 if reflect.TypeOf(st).Elem().Kind() != reflect.Struct { // 如果传入的也不是对象 return errors.New("first param is not pointer, error!") } } if settings == nil { return errors.New("settings is nil, nothing to do") } var ( field reflect.StructField // reflect中的预定义类型 ok bool ) for t,v := range settings { // 用map的元素给属性赋值 fmt.Println(t) // Name fmt.Println(reflect.ValueOf(st)) // &{ 0} fmt.Println((reflect.ValueOf(st)).Elem()) // { 0} fmt.Println((reflect.ValueOf(st)).Elem().Type()) // myreflect.Empolyee fmt.Println((reflect.ValueOf(st)).Elem().Type().FieldByName(t)) // {Name string format:"normal" 16 [1] false} true if field, ok = (reflect.ValueOf(st)).Elem().Type().FieldByName(t); !ok { // 如果找不到和map中键名称相一致的属性名称 continue } if field.Type == reflect.TypeOf(v) { // 有这个属性名,而且属性类型也和map中值的类型相同,可以赋值了 vstr := reflect.ValueOf(st) vstr = vstr.Elem() vstr.FieldByName(t).Set(reflect.ValueOf(v)) // 设置成map的value部分,不能直接写v } } return nil } // 还是用到之前定义的Employee和Customer struct func TestFillSettings(t *testing.T) { settings := map[string]interface{}{"Name":"Roy", "Age":18} e := Empolyee{} 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) // { Roy 18} }上面的程序实现了对空对象属性的赋值 *** 作反射的缺点在于代码的可读性降低 不安全编程
go语言不支持强制类型转换,但也不是绝对不行,在unsafe中提供了方法
package myunsafe import "unsafe" import "testing" func TestUnsafe(t *testing.T) { i := 10 t.Log(unsafe.Pointer(&i)) // 0xc0000121a8 f := *(*float64)(unsafe.Pointer(&i)) t.Log(unsafe.Pointer(&i)) // 查看内存地址 0xc0000121a8 t.Log(f) // 5e-323 完全不是10了 } type MyInt int func TestConvert(t *testing.T) { a := []int{1,2,3,4} // slice类型 t.Log(a) // [1 2 3 4] b := *(*[]MyInt)(unsafe.Pointer(&a)) // 强制转换 t.Log(b) // [1 2 3 4] 这个可以,只是起了个别名 }这很明显是不推荐的 原子 *** 作,保证线程安全的一种方式
package myunsafe import "unsafe" import "testing" import "sync/atomic" import "sync" import "time" import "fmt" func TestAtomic(t *testing.T) { var shareBuffer unsafe.Pointer t.Logf("%[1]v, %[1]T", &shareBuffer) // 0xc000006038, *unsafe.Pointer writeData := func() { // 闭包 data := []int{} for i:=0; i<100; i++ { data = append(data, i) } // 这个切换必须是线程安全的,这里用原子操作实现 atomic.StorePointer(&shareBuffer, unsafe.Pointer(&data)) // 切换到共享的buffer,存 } readData := func() { data := atomic.LoadPointer(&shareBuffer) // 切换到共享的buffer,读 fmt.Println(data, *(*[]int)(data)) // 强转,输出指针和内容 } var wg sync.WaitGroup writeData() // 先写一个,不然读不出来报错 for i:=0; i<2; i++ { wg.Add(1) go func() { for i:=0; i<4; i++ { // 一个协程里多次读写,形成并发测试环境 writeData() time.Sleep(time.Microsecond * 100) } wg.Done() // -1 }() wg.Add(1) go func() { for i:=0; i<4; i++ { readData() time.Sleep(time.Microsecond * 100) } wg.Done() }() } wg.Wait() readData() // 读最后那个读到的StorePointer处的数据 0xc000004018 }& 是取地址符号 , 即取得某个变量的地址 , 如 ; &a* 是指针运算符 , 可以表示一个变量是指针类型 , 也可以表示一个指针变量所指向的存储单元 , 也就是这个地址所存储的值
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)