Go——单元测试

Go——单元测试,第1张

Go——单元测试

文章目录

测试

单元测试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* 是指针运算符 , 可以表示一个变量是指针类型 , 也可以表示一个指针变量所指向的存储单元 , 也就是这个地址所存储的值

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5719286.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存