赞
踩
目录
Go语言中自带有一个轻量级的测试框架testing和自带的go test命令来实现单元测试和性能测试,testing框架和其他语言的测试框架相似,可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例。通过单元测试,可以解决:
- 确保每个函数是可运行,并且运行结果是正确的
- 确保写出来的代码性能是好的
- 单元测试能及时的发现程序设计或实现的逻辑错误,使问题暴露,便于问题的定位解决,而性能测试的重点在于发现程序设计上的一些问题,让程序能够在高并发的情况下还能保持稳定
Go 语言推荐测试文件和源代码文件放在一块,测试文件以
_test.go
结尾
注意点:
- 测试用例文件名必须以_test.go结尾
- 测试用例函数必须以Test开头,一般来说就是Test+被测试的函数名
例如我现在有两个用于计算的文件,叫calc.go
- package main
-
- func Add(a int, b int) int {
- return a + b
- }
-
- func Mul(a int, b int) int {
- return a * b
- }
那么我的测试文件就是calc_test.go
- package main
-
- import "testing"
-
- func TestAdd(t *testing.T) {
- if ans := Add(1, 2); ans != 3 {
- // 如果不符合预期,那就是测试不通过
- t.Errorf("1 + 2 expected be 3, but %d got", ans)
- }
-
- if ans := Add(-10, -20); ans != -30 {
- t.Errorf("-10 + -20 expected be -30, but %d got", ans)
- }
- }
go test // 可以运行某个包下的所有测试用例
-v
参数会显示每个用例的测试结果
-run
参数可以指定测试某个函数
单元测试框架提供的日志方法
方 法 | 备 注 | 测试结果 |
---|---|---|
Log | 打印日志,同时结束测试 | PASS |
Logf | 格式化打印日志,同时结束测试 | PASS |
Error | 打印错误日志,同时结束测试 | FAIL |
Errorf | 格式化打印错误日志,同时结束测试 | FAIL |
Fatal | 打印致命日志,同时结束测试 | FAIL |
Fatalf | 格式化打印致命日志,同时结束测试 | FAIL |
如果需要给一个函数,调用不同的测试用例,可以使用子测试
子测试里面的Fatal,是不会终止程序的
- package main
-
- import "testing"
-
- func TestAdd(t1 *testing.T) {
- t1.Run("add1", func(t *testing.T) {
- if ans := Add(1, 2); ans != 3 {
- // 如果不符合预期,那就是测试不通过
- t.Fatalf("1 + 2 expected be 3, but %d got", ans)
- }
- })
- t1.Run("add2", func(t *testing.T) {
- if ans := Add(-10, -20); ans != -30 {
- t.Fatalf("-10 + -20 expected be -30, but %d got", ans)
- }
- })
-
- }
如果测试用例很多,还可以用一个类似表格去表示
- package main
-
- import (
- "testing"
- )
-
- func TestAdd(t *testing.T) {
- cases := []struct {
- Name string
- A, B, Expected int
- }{
- {"a1", 2, 3, 5},
- {"a2", 2, -3, -1},
- {"a3", 2, 0, 2},
- }
-
- for _, c := range cases {
- t.Run(c.Name, func(t *testing.T) {
- if ans := Add(c.A, c.B); ans != c.Expected {
- t.Fatalf("%d * %d expected %d, but %d got",
- c.A, c.B, c.Expected, ans)
- }
- })
- }
- }
它是测试的入口
我们可以在TestMain里面实现测试流程的生命周期
- package main
-
- import (
- "fmt"
- "os"
- "testing"
- )
-
- // 测试前执行
- func setup() {
- fmt.Println("Before all tests")
- }
-
- // 测试后执行
- func teardown() {
- fmt.Println("After all tests")
- }
-
- func Test1(t *testing.T) {
- fmt.Println("I'm test1")
- }
-
- func Test2(t *testing.T) {
- fmt.Println("I'm test2")
- }
-
- // 必须叫这个名字 测试主入口
- func TestMain(m *testing.M) {
- // 测试前执行
- setup()
- code := m.Run()
- // 测试后执行
- teardown()
- os.Exit(code)
- }
判断一个变量是否是结构体,切片,map
- package main
-
- import (
- "fmt"
- "reflect"
- )
-
- func refType(obj any) {
- typeObj := reflect.TypeOf(obj)
- fmt.Println(typeObj, "+", typeObj.Kind())
- // 去判断具体的类型
- switch typeObj.Kind() {
- case reflect.Slice:
- fmt.Println("切片")
- case reflect.Map:
- fmt.Println("map")
- case reflect.Struct:
- fmt.Println("结构体")
- case reflect.String:
- fmt.Println("字符串")
- }
- }
-
- func main() {
- refType(struct{ Name string }{Name: "os_lee"})
- name := "os_lee"
- refType(name)
- refType([]string{"os_lee"})
- }
- package main
-
- import (
- "fmt"
- "reflect"
- )
-
- func refValue(obj any) {
- value := reflect.ValueOf(obj)
- fmt.Println(value, "+", value.Type())
- switch value.Kind() {
- case reflect.Int:
- fmt.Println("Int=", value.Int())
- case reflect.Struct:
- fmt.Println("Interface=", value.Interface())
- case reflect.String:
- fmt.Println("String=", value.String())
- }
- }
-
- func main() {
- refValue(struct{ Name string }{Name: "os_lee"})
- name := "os_lee"
- refValue(name)
- refValue([]string{"os_lee"})
- }
注意,如果需要通过反射修改值,必须要传指针,在反射中使用Elem取指针对应的值
读取json标签对应的值,如果没有就用属性的名称
这个示例很简单,没有处理-和omitempty的情况
- package main
-
- import (
- "fmt"
- "reflect"
- )
-
- type Student struct {
- Name string
- Age int `json:"age"`
- }
-
- func main() {
- s := Student{
- Name: "os_lee",
- Age: 24,
- }
- t := reflect.TypeOf(s)
- v := reflect.ValueOf(s)
-
- for i := 0; i < t.NumField(); i++ {
- field := t.Field(i)
- jsonField := field.Tag.Get("json")
- if jsonField == "" {
- // 说明json的tag是空的
- jsonField = field.Name
- }
- fmt.Printf("Name: %s, type: %s, json: %s, value: %v\n", field.Name, field.Type, jsonField, v.Field(i))
- }
- }
例如,结构体tag中有big的标签,就将值大写
- package main
-
- import (
- "fmt"
- "reflect"
- "strings"
- )
-
- type Student struct {
- Name string `big:"name"`
- Addr string
- }
-
- func main() {
- s := Student{
- Name: "os",
- Addr: "bj",
- }
- t := reflect.TypeOf(s)
- v := reflect.ValueOf(&s).Elem()
-
- for i := 0; i < t.NumField(); i++ {
- field := t.Field(i)
- bigField := field.Tag.Get("big")
- // 判断类型是不是字符串
- if field.Type.Kind() != reflect.String {
- continue
- }
- if bigField == "" {
- continue
- }
- // 修改值
- valueFiled := v.Field(i)
- valueFiled.SetString(strings.ToTitle(valueFiled.String()))
- }
- fmt.Println(s)
- }
如果结构体有call这个名字的方法,就执行它
- package main
-
- import (
- "fmt"
- "reflect"
- )
-
- type Student struct {
- Name string
- Age int
- }
-
- func (Student) Look(name string) {
- fmt.Println("look name:", name)
- }
-
- func (Student) See(name string) {
- fmt.Println("see name:", name)
- }
-
- func main() {
- s := Student{
- Name: "os",
- Age: 21,
- }
- t := reflect.TypeOf(s)
- v := reflect.ValueOf(s)
-
- for i := 0; i < t.NumMethod(); i++ {
- methodType := t.Method(i)
- fmt.Println(methodType.Name, methodType.Type)
- if methodType.Name != "See" {
- continue
- }
- methodValue := v.Method(i)
- methodValue.Call([]reflect.Value{
- reflect.ValueOf("lee"), // 注意这里的类型
- })
- }
- }
- package main
-
- import (
- "errors"
- "fmt"
- "reflect"
- "strings"
- )
-
- type Student struct {
- Name string `oslee-orm:"name"`
- Age int `oslee-orm:"age"`
- }
-
- type UserInfo struct {
- Id int `oslee-orm:"id"`
- Name string `oslee-orm:"name"`
- Age int `oslee-orm:"age"`
- }
-
- // sql, err := Find(Student{}, "name = ? and age = ?", "os_lee", 18)
- func Find(obj any, query ...any) (sql string, err error) {
- // Find(Student, "name = ?", "os")
- // 希望能够生成 select name, age from where name = 'os'
- t := reflect.TypeOf(obj)
- //v := reflect.ValueOf(obj)
- // 首先得是结构体对吧
- if t.Kind() != reflect.Struct {
- err = errors.New("非结构体")
- return
- }
- // 拿全部字段
-
- // 拼接条件
- // 第二个参数,中的问号,就决定后面还能接多少参数
- var where string
- if len(query) > 0 {
- // 有第二个参数,校验第二个参数中的?个数,是不是和后面的个数一样
- q := query[0] // 理论上还要校验第二个参数的类型
- if strings.Count(q.(string), "?")+1 != len(query) {
- err = errors.New("参数个数不对")
- return
- }
- // 拼接where语句
- // 将?号带入后面的参数
- for _, a := range query[1:] {
- // 替换q
- // 这里要判断a的类型
- at := reflect.TypeOf(a)
- switch at.Kind() {
- case reflect.Int:
- q = strings.Replace(q.(string), "?", fmt.Sprintf("%d", a.(int)), 1)
- case reflect.String:
- q = strings.Replace(q.(string), "?", fmt.Sprintf("'%s'", a.(string)), 1)
- }
- }
- where += "where " + q.(string)
- }
- // 如果没有第二个参数,就是查全部
-
- // 拼接select
- // 拿所有字段,取oslee-orm对应的值
- var columns []string
- for i := 0; i < t.NumField(); i++ {
- field := t.Field(i)
- f := field.Tag.Get("oslee-orm")
- // 不考虑是空的情况
- columns = append(columns, f)
- }
-
- // 结构体的小写名字+s做表名
- name := strings.ToLower(t.Name()) + "s"
-
- // 拼接最后的sql
- sql = fmt.Sprintf("select %s from %s %s", strings.Join(columns, ","), name, where)
- return
- }
-
- func main() {
- sql, err := Find(Student{}, "name = ? and age = ?", "os_lee", 18)
- fmt.Println(sql, err) // select name,age from students where name = 'os_lee' and age = 18
- sql, err = Find(UserInfo{}, "id = ?", 1)
- fmt.Println(sql, err) // select id,name,age from userinfos where id = 1
- }
如果是写一下框架,偏底层工具类的操作
不用反射确实不太好写,但是如果是在业务上,大量使用反射就不太合适了
因为反射的性能没有正常代码高,会慢个一到两个数量级
使用反射可读性也不太好,并且也不能在编译期间发生错误
参考:5.网络编程-socker(golang版)-CSDN博客
参考: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是无效的
- set CGO_ENABLED=0
- set GOOS=linux
- set GOARCH=amd64
- 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服务器下,设置文件权限就可以直接运行了
- chmod +x main
- ./main
再次注意啊,以后打包web项目的时候,配置文件和静态文件等这些非go程序,是要一起复制到目标服务器里面的
参考:Go 学习笔记(37)— 标准命令行工具(go build 跨平台编译、交叉编译、go clean、go run、go fmt、go install、go get、go vet)-CSDN博客
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。