当前位置:   article > 正文

Golang教程六(单元测试,反射,网络编程,部署)

Golang教程六(单元测试,反射,网络编程,部署)

目录

一、单元测试

单元测试 

 子测试

 TestMain

二、反射

类型判断

通过反射获取值

通过反射修改值

结构体反射

利用tag修改结构体的某些值

调用结构体方法

orm的一个小案例

对反射的一些建议

三、网络编程

socket编程

websocket编程

四、部署

打包命令

交叉编译


一、单元测试

Go语言中自带有一个轻量级的测试框架testing和自带的go test命令来实现单元测试和性能测试,testing框架和其他语言的测试框架相似,可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例。通过单元测试,可以解决:

  1. 确保每个函数是可运行,并且运行结果是正确的
  2. 确保写出来的代码性能是好的
  3. 单元测试能及时的发现程序设计或实现的逻辑错误,使问题暴露,便于问题的定位解决,而性能测试的重点在于发现程序设计上的一些问题,让程序能够在高并发的情况下还能保持稳定

Go 语言推荐测试文件和源代码文件放在一块,测试文件以 _test.go 结尾

注意点:

  1. 测试用例文件名必须以_test.go结尾
  2. 测试用例函数必须以Test开头,一般来说就是Test+被测试的函数名 

单元测试 

例如我现在有两个用于计算的文件,叫calc.go

  1. package main
  2. func Add(a int, b int) int {
  3. return a + b
  4. }
  5. func Mul(a int, b int) int {
  6. return a * b
  7. }

那么我的测试文件就是calc_test.go

  1. package main
  2. import "testing"
  3. func TestAdd(t *testing.T) {
  4. if ans := Add(1, 2); ans != 3 {
  5. // 如果不符合预期,那就是测试不通过
  6. t.Errorf("1 + 2 expected be 3, but %d got", ans)
  7. }
  8. if ans := Add(-10, -20); ans != -30 {
  9. t.Errorf("-10 + -20 expected be -30, but %d got", ans)
  10. }
  11. }

go test // 可以运行某个包下的所有测试用例

-v 参数会显示每个用例的测试结果

-run参数可以指定测试某个函数

单元测试框架提供的日志方法

方 法备 注测试结果
Log打印日志,同时结束测试PASS
Logf格式化打印日志,同时结束测试PASS
Error打印错误日志,同时结束测试FAIL
Errorf格式化打印错误日志,同时结束测试FAIL
Fatal打印致命日志,同时结束测试FAIL
Fatalf格式化打印致命日志,同时结束测试FAIL

 子测试

如果需要给一个函数,调用不同的测试用例,可以使用子测试

子测试里面的Fatal,是不会终止程序的

  1. package main
  2. import "testing"
  3. func TestAdd(t1 *testing.T) {
  4. t1.Run("add1", func(t *testing.T) {
  5. if ans := Add(1, 2); ans != 3 {
  6. // 如果不符合预期,那就是测试不通过
  7. t.Fatalf("1 + 2 expected be 3, but %d got", ans)
  8. }
  9. })
  10. t1.Run("add2", func(t *testing.T) {
  11. if ans := Add(-10, -20); ans != -30 {
  12. t.Fatalf("-10 + -20 expected be -30, but %d got", ans)
  13. }
  14. })
  15. }

如果测试用例很多,还可以用一个类似表格去表示

  1. package main
  2. import (
  3. "testing"
  4. )
  5. func TestAdd(t *testing.T) {
  6. cases := []struct {
  7. Name string
  8. A, B, Expected int
  9. }{
  10. {"a1", 2, 3, 5},
  11. {"a2", 2, -3, -1},
  12. {"a3", 2, 0, 2},
  13. }
  14. for _, c := range cases {
  15. t.Run(c.Name, func(t *testing.T) {
  16. if ans := Add(c.A, c.B); ans != c.Expected {
  17. t.Fatalf("%d * %d expected %d, but %d got",
  18. c.A, c.B, c.Expected, ans)
  19. }
  20. })
  21. }
  22. }

 TestMain

它是测试的入口

我们可以在TestMain里面实现测试流程的生命周期

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "testing"
  6. )
  7. // 测试前执行
  8. func setup() {
  9. fmt.Println("Before all tests")
  10. }
  11. // 测试后执行
  12. func teardown() {
  13. fmt.Println("After all tests")
  14. }
  15. func Test1(t *testing.T) {
  16. fmt.Println("I'm test1")
  17. }
  18. func Test2(t *testing.T) {
  19. fmt.Println("I'm test2")
  20. }
  21. // 必须叫这个名字 测试主入口
  22. func TestMain(m *testing.M) {
  23. // 测试前执行
  24. setup()
  25. code := m.Run()
  26. // 测试后执行
  27. teardown()
  28. os.Exit(code)
  29. }

二、反射

类型判断

判断一个变量是否是结构体,切片,map

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. func refType(obj any) {
  7. typeObj := reflect.TypeOf(obj)
  8. fmt.Println(typeObj, "+", typeObj.Kind())
  9. // 去判断具体的类型
  10. switch typeObj.Kind() {
  11. case reflect.Slice:
  12. fmt.Println("切片")
  13. case reflect.Map:
  14. fmt.Println("map")
  15. case reflect.Struct:
  16. fmt.Println("结构体")
  17. case reflect.String:
  18. fmt.Println("字符串")
  19. }
  20. }
  21. func main() {
  22. refType(struct{ Name string }{Name: "os_lee"})
  23. name := "os_lee"
  24. refType(name)
  25. refType([]string{"os_lee"})
  26. }

通过反射获取值

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. func refValue(obj any) {
  7. value := reflect.ValueOf(obj)
  8. fmt.Println(value, "+", value.Type())
  9. switch value.Kind() {
  10. case reflect.Int:
  11. fmt.Println("Int=", value.Int())
  12. case reflect.Struct:
  13. fmt.Println("Interface=", value.Interface())
  14. case reflect.String:
  15. fmt.Println("String=", value.String())
  16. }
  17. }
  18. func main() {
  19. refValue(struct{ Name string }{Name: "os_lee"})
  20. name := "os_lee"
  21. refValue(name)
  22. refValue([]string{"os_lee"})
  23. }

通过反射修改值

注意,如果需要通过反射修改值,必须要传指针,在反射中使用Elem取指针对应的值

结构体反射

读取json标签对应的值,如果没有就用属性的名称

这个示例很简单,没有处理-和omitempty的情况

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type Student struct {
  7. Name string
  8. Age int `json:"age"`
  9. }
  10. func main() {
  11. s := Student{
  12. Name: "os_lee",
  13. Age: 24,
  14. }
  15. t := reflect.TypeOf(s)
  16. v := reflect.ValueOf(s)
  17. for i := 0; i < t.NumField(); i++ {
  18. field := t.Field(i)
  19. jsonField := field.Tag.Get("json")
  20. if jsonField == "" {
  21. // 说明json的tag是空的
  22. jsonField = field.Name
  23. }
  24. fmt.Printf("Name: %s, type: %s, json: %s, value: %v\n", field.Name, field.Type, jsonField, v.Field(i))
  25. }
  26. }

利用tag修改结构体的某些值

例如,结构体tag中有big的标签,就将值大写

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. "strings"
  6. )
  7. type Student struct {
  8. Name string `big:"name"`
  9. Addr string
  10. }
  11. func main() {
  12. s := Student{
  13. Name: "os",
  14. Addr: "bj",
  15. }
  16. t := reflect.TypeOf(s)
  17. v := reflect.ValueOf(&s).Elem()
  18. for i := 0; i < t.NumField(); i++ {
  19. field := t.Field(i)
  20. bigField := field.Tag.Get("big")
  21. // 判断类型是不是字符串
  22. if field.Type.Kind() != reflect.String {
  23. continue
  24. }
  25. if bigField == "" {
  26. continue
  27. }
  28. // 修改值
  29. valueFiled := v.Field(i)
  30. valueFiled.SetString(strings.ToTitle(valueFiled.String()))
  31. }
  32. fmt.Println(s)
  33. }

调用结构体方法

如果结构体有call这个名字的方法,就执行它

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type Student struct {
  7. Name string
  8. Age int
  9. }
  10. func (Student) Look(name string) {
  11. fmt.Println("look name:", name)
  12. }
  13. func (Student) See(name string) {
  14. fmt.Println("see name:", name)
  15. }
  16. func main() {
  17. s := Student{
  18. Name: "os",
  19. Age: 21,
  20. }
  21. t := reflect.TypeOf(s)
  22. v := reflect.ValueOf(s)
  23. for i := 0; i < t.NumMethod(); i++ {
  24. methodType := t.Method(i)
  25. fmt.Println(methodType.Name, methodType.Type)
  26. if methodType.Name != "See" {
  27. continue
  28. }
  29. methodValue := v.Method(i)
  30. methodValue.Call([]reflect.Value{
  31. reflect.ValueOf("lee"), // 注意这里的类型
  32. })
  33. }
  34. }

orm的一个小案例

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "reflect"
  6. "strings"
  7. )
  8. type Student struct {
  9. Name string `oslee-orm:"name"`
  10. Age int `oslee-orm:"age"`
  11. }
  12. type UserInfo struct {
  13. Id int `oslee-orm:"id"`
  14. Name string `oslee-orm:"name"`
  15. Age int `oslee-orm:"age"`
  16. }
  17. // sql, err := Find(Student{}, "name = ? and age = ?", "os_lee", 18)
  18. func Find(obj any, query ...any) (sql string, err error) {
  19. // Find(Student, "name = ?", "os")
  20. // 希望能够生成 select name, age from where name = 'os'
  21. t := reflect.TypeOf(obj)
  22. //v := reflect.ValueOf(obj)
  23. // 首先得是结构体对吧
  24. if t.Kind() != reflect.Struct {
  25. err = errors.New("非结构体")
  26. return
  27. }
  28. // 拿全部字段
  29. // 拼接条件
  30. // 第二个参数,中的问号,就决定后面还能接多少参数
  31. var where string
  32. if len(query) > 0 {
  33. // 有第二个参数,校验第二个参数中的?个数,是不是和后面的个数一样
  34. q := query[0] // 理论上还要校验第二个参数的类型
  35. if strings.Count(q.(string), "?")+1 != len(query) {
  36. err = errors.New("参数个数不对")
  37. return
  38. }
  39. // 拼接where语句
  40. // 将?号带入后面的参数
  41. for _, a := range query[1:] {
  42. // 替换q
  43. // 这里要判断a的类型
  44. at := reflect.TypeOf(a)
  45. switch at.Kind() {
  46. case reflect.Int:
  47. q = strings.Replace(q.(string), "?", fmt.Sprintf("%d", a.(int)), 1)
  48. case reflect.String:
  49. q = strings.Replace(q.(string), "?", fmt.Sprintf("'%s'", a.(string)), 1)
  50. }
  51. }
  52. where += "where " + q.(string)
  53. }
  54. // 如果没有第二个参数,就是查全部
  55. // 拼接select
  56. // 拿所有字段,取oslee-orm对应的值
  57. var columns []string
  58. for i := 0; i < t.NumField(); i++ {
  59. field := t.Field(i)
  60. f := field.Tag.Get("oslee-orm")
  61. // 不考虑是空的情况
  62. columns = append(columns, f)
  63. }
  64. // 结构体的小写名字+s做表名
  65. name := strings.ToLower(t.Name()) + "s"
  66. // 拼接最后的sql
  67. sql = fmt.Sprintf("select %s from %s %s", strings.Join(columns, ","), name, where)
  68. return
  69. }
  70. func main() {
  71. sql, err := Find(Student{}, "name = ? and age = ?", "os_lee", 18)
  72. fmt.Println(sql, err) // select name,age from students where name = 'os_lee' and age = 18
  73. sql, err = Find(UserInfo{}, "id = ?", 1)
  74. fmt.Println(sql, err) // select id,name,age from userinfos where id = 1
  75. }

对反射的一些建议

如果是写一下框架,偏底层工具类的操作

不用反射确实不太好写,但是如果是在业务上,大量使用反射就不太合适了

因为反射的性能没有正常代码高,会慢个一到两个数量级

使用反射可读性也不太好,并且也不能在编译期间发生错误

三、网络编程

socket编程

参考:5.网络编程-socker(golang版)-CSDN博客

websocket编程

参考:4.网络编程-websocket(golang)-CSDN博客

四、部署

go项目的部署特别简单,编写完成之后,只需要执行go build即可打包为可执行文件
注意,这个操作是不同平台不一样的
windows下打包就是exe文件,linux下打包就是二进制文件

打包命令

go build

打当前目录下的main包,注意,只能有一个main函数的包

go build xxx.go

打当前目录下,xxx.go的包,这个包必须得是一个main包,不然没有效果

go build -o main.exe xxx.go

强制对输出的文件进行重命名

-o参数必须得在文件的前面

交叉编译

什么是交叉编译呢,就是在windows上,我开发的go程序,我也能打包为linux上的可执行程序

例如在windows平台,打linux的包

注意,执行set这个命令,一定得要是在cmd的命令行下,powershell是无效的 

  1. set CGO_ENABLED=0
  2. set GOOS=linux
  3. set GOARCH=amd64
  4. go build -o main main.go

 CGO_ENABLED : CGO 表示 golang 中的工具,CGO_ENABLED=0 表示 CGO 禁用,交叉编译中不能使用 CGO GOOS : 环境变量用于指定目标操作系统,mac 对应 darwin,linux 对应 linux,windows 对应 windows ,还有其它的 freebsd、android 等

GOARCH:环境变量用于指定处理器的类型,386 也称 x86 对应 32位操作系统、amd64 也称 x64 对应 64 位操作系统,arm 这种架构一般用于嵌入式开发。比如 Android , iOS , Win mobile 等

为了方便呢,可以在项目的根目录下写一个bat文件

这样就能快速构建了

然后放到linux服务器下,设置文件权限就可以直接运行了

  1. chmod +x main
  2. ./main

再次注意啊,以后打包web项目的时候,配置文件和静态文件等这些非go程序,是要一起复制到目标服务器里面的

参考:Go 学习笔记(37)— 标准命令行工具(go build 跨平台编译、交叉编译、go clean、go run、go fmt、go install、go get、go vet)-CSDN博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/446492
推荐阅读
相关标签
  

闽ICP备14008679号