做網(wǎng)站開發(fā)的女生多嗎百度推廣競(jìng)價(jià)是什么意思
目錄
一、單元測(cè)試
單元測(cè)試?
?子測(cè)試
?TestMain
二、反射
類型判斷
通過反射獲取值
通過反射修改值
結(jié)構(gòu)體反射
利用tag修改結(jié)構(gòu)體的某些值
調(diào)用結(jié)構(gòu)體方法
orm的一個(gè)小案例
對(duì)反射的一些建議
三、網(wǎng)絡(luò)編程
socket編程
websocket編程
四、部署
打包命令
交叉編譯
一、單元測(cè)試
Go語(yǔ)言中自帶有一個(gè)輕量級(jí)的測(cè)試框架testing和自帶的go test命令來(lái)實(shí)現(xiàn)單元測(cè)試和性能測(cè)試,testing框架和其他語(yǔ)言的測(cè)試框架相似,可以基于這個(gè)框架寫針對(duì)相應(yīng)函數(shù)的測(cè)試用例,也可以基于該框架寫相應(yīng)的壓力測(cè)試用例。通過單元測(cè)試,可以解決:
- 確保每個(gè)函數(shù)是可運(yùn)行,并且運(yùn)行結(jié)果是正確的
- 確保寫出來(lái)的代碼性能是好的
- 單元測(cè)試能及時(shí)的發(fā)現(xiàn)程序設(shè)計(jì)或?qū)崿F(xiàn)的邏輯錯(cuò)誤,使問題暴露,便于問題的定位解決,而性能測(cè)試的重點(diǎn)在于發(fā)現(xiàn)程序設(shè)計(jì)上的一些問題,讓程序能夠在高并發(fā)的情況下還能保持穩(wěn)定
Go 語(yǔ)言推薦測(cè)試文件和源代碼文件放在一塊,測(cè)試文件以?
_test.go
?結(jié)尾
注意點(diǎn):
- 測(cè)試用例文件名必須以_test.go結(jié)尾
- 測(cè)試用例函數(shù)必須以Test開頭,一般來(lái)說就是Test+被測(cè)試的函數(shù)名?
單元測(cè)試?
例如我現(xiàn)在有兩個(gè)用于計(jì)算的文件,叫calc.go
package mainfunc Add(a int, b int) int {return a + b
}func Mul(a int, b int) int {return a * b
}
那么我的測(cè)試文件就是calc_test.go
package mainimport "testing"func TestAdd(t *testing.T) {if ans := Add(1, 2); ans != 3 {// 如果不符合預(yù)期,那就是測(cè)試不通過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 // 可以運(yùn)行某個(gè)包下的所有測(cè)試用例
-v
?參數(shù)會(huì)顯示每個(gè)用例的測(cè)試結(jié)果
-run
參數(shù)可以指定測(cè)試某個(gè)函數(shù)
單元測(cè)試框架提供的日志方法
方 法 | 備 注 | 測(cè)試結(jié)果 |
---|---|---|
Log | 打印日志,同時(shí)結(jié)束測(cè)試 | PASS |
Logf | 格式化打印日志,同時(shí)結(jié)束測(cè)試 | PASS |
Error | 打印錯(cuò)誤日志,同時(shí)結(jié)束測(cè)試 | FAIL |
Errorf | 格式化打印錯(cuò)誤日志,同時(shí)結(jié)束測(cè)試 | FAIL |
Fatal | 打印致命日志,同時(shí)結(jié)束測(cè)試 | FAIL |
Fatalf | 格式化打印致命日志,同時(shí)結(jié)束測(cè)試 | FAIL |
?子測(cè)試
如果需要給一個(gè)函數(shù),調(diào)用不同的測(cè)試用例,可以使用子測(cè)試
子測(cè)試?yán)锩娴腇atal,是不會(huì)終止程序的
package mainimport "testing"func TestAdd(t1 *testing.T) {t1.Run("add1", func(t *testing.T) {if ans := Add(1, 2); ans != 3 {// 如果不符合預(yù)期,那就是測(cè)試不通過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)}})}
如果測(cè)試用例很多,還可以用一個(gè)類似表格去表示
package mainimport ("testing"
)func TestAdd(t *testing.T) {cases := []struct {Name stringA, 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
它是測(cè)試的入口
我們可以在TestMain里面實(shí)現(xiàn)測(cè)試流程的生命周期
package mainimport ("fmt""os""testing"
)// 測(cè)試前執(zhí)行
func setup() {fmt.Println("Before all tests")
}// 測(cè)試后執(zhí)行
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")
}// 必須叫這個(gè)名字 測(cè)試主入口
func TestMain(m *testing.M) {// 測(cè)試前執(zhí)行setup()code := m.Run()// 測(cè)試后執(zhí)行teardown()os.Exit(code)
}
二、反射
類型判斷
判斷一個(gè)變量是否是結(jié)構(gòu)體,切片,map
package mainimport ("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("結(jié)構(gòu)體")case reflect.String:fmt.Println("字符串")}
}func main() {refType(struct{ Name string }{Name: "os_lee"})name := "os_lee"refType(name)refType([]string{"os_lee"})
}
通過反射獲取值
package mainimport ("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取指針對(duì)應(yīng)的值
結(jié)構(gòu)體反射
讀取json標(biāo)簽對(duì)應(yīng)的值,如果沒有就用屬性的名稱
這個(gè)示例很簡(jiǎn)單,沒有處理-和omitempty的情況
package mainimport ("fmt""reflect"
)type Student struct {Name stringAge 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修改結(jié)構(gòu)體的某些值
例如,結(jié)構(gòu)體tag中有big的標(biāo)簽,就將值大寫
package mainimport ("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)
}
調(diào)用結(jié)構(gòu)體方法
如果結(jié)構(gòu)體有call這個(gè)名字的方法,就執(zhí)行它
package mainimport ("fmt""reflect"
)type Student struct {Name stringAge 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"), // 注意這里的類型})}
}
orm的一個(gè)小案例
package mainimport ("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)// 首先得是結(jié)構(gòu)體對(duì)吧if t.Kind() != reflect.Struct {err = errors.New("非結(jié)構(gòu)體")return}// 拿全部字段// 拼接條件// 第二個(gè)參數(shù),中的問號(hào),就決定后面還能接多少參數(shù)var where stringif len(query) > 0 {// 有第二個(gè)參數(shù),校驗(yàn)第二個(gè)參數(shù)中的?個(gè)數(shù),是不是和后面的個(gè)數(shù)一樣q := query[0] // 理論上還要校驗(yàn)第二個(gè)參數(shù)的類型if strings.Count(q.(string), "?")+1 != len(query) {err = errors.New("參數(shù)個(gè)數(shù)不對(duì)")return}// 拼接where語(yǔ)句// 將?號(hào)帶入后面的參數(shù)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)}// 如果沒有第二個(gè)參數(shù),就是查全部// 拼接select// 拿所有字段,取oslee-orm對(duì)應(yīng)的值var columns []stringfor i := 0; i < t.NumField(); i++ {field := t.Field(i)f := field.Tag.Get("oslee-orm")// 不考慮是空的情況columns = append(columns, f)}// 結(jié)構(gòu)體的小寫名字+s做表名name := strings.ToLower(t.Name()) + "s"// 拼接最后的sqlsql = 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 = 18sql, err = Find(UserInfo{}, "id = ?", 1)fmt.Println(sql, err) // select id,name,age from userinfos where id = 1
}
對(duì)反射的一些建議
如果是寫一下框架,偏底層工具類的操作
不用反射確實(shí)不太好寫,但是如果是在業(yè)務(wù)上,大量使用反射就不太合適了
因?yàn)榉瓷涞男阅軟]有正常代碼高,會(huì)慢個(gè)一到兩個(gè)數(shù)量級(jí)
使用反射可讀性也不太好,并且也不能在編譯期間發(fā)生錯(cuò)誤
三、網(wǎng)絡(luò)編程
socket編程
參考:5.網(wǎng)絡(luò)編程-socker(golang版)-CSDN博客
websocket編程
參考:4.網(wǎng)絡(luò)編程-websocket(golang)-CSDN博客
四、部署
go項(xiàng)目的部署特別簡(jiǎn)單,編寫完成之后,只需要執(zhí)行g(shù)o build即可打包為可執(zhí)行文件
注意,這個(gè)操作是不同平臺(tái)不一樣的
windows下打包就是exe文件,linux下打包就是二進(jìn)制文件
打包命令
go build
打當(dāng)前目錄下的main包,注意,只能有一個(gè)main函數(shù)的包
go build xxx.go
打當(dāng)前目錄下,xxx.go的包,這個(gè)包必須得是一個(gè)main包,不然沒有效果
go build -o main.exe xxx.go
強(qiáng)制對(duì)輸出的文件進(jìn)行重命名
-o參數(shù)必須得在文件的前面
交叉編譯
什么是交叉編譯呢,就是在windows上,我開發(fā)的go程序,我也能打包為linux上的可執(zhí)行程序
例如在windows平臺(tái),打linux的包
注意,執(zhí)行set這個(gè)命令,一定得要是在cmd的命令行下,powershell是無(wú)效的?
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 : 環(huán)境變量用于指定目標(biāo)操作系統(tǒng),mac 對(duì)應(yīng) darwin,linux 對(duì)應(yīng) linux,windows 對(duì)應(yīng) windows ,還有其它的 freebsd、android 等
GOARCH
:環(huán)境變量用于指定處理器的類型,386 也稱?x86
?對(duì)應(yīng) 32位操作系統(tǒng)、amd64
?也稱 x64 對(duì)應(yīng) 64 位操作系統(tǒng),arm
?這種架構(gòu)一般用于嵌入式開發(fā)。比如?Android
?,?iOS
?,?Win mobile
?等為了方便呢,可以在項(xiàng)目的根目錄下寫一個(gè)bat文件
這樣就能快速構(gòu)建了
然后放到linux服務(wù)器下,設(shè)置文件權(quán)限就可以直接運(yùn)行了
chmod +x main
./main
再次注意啊,以后打包web項(xiàng)目的時(shí)候,配置文件和靜態(tài)文件等這些非go程序,是要一起復(fù)制到目標(biāo)服務(wù)器里面的
參考:Go 學(xué)習(xí)筆記(37)— 標(biāo)準(zhǔn)命令行工具(go build 跨平臺(tái)編譯、交叉編譯、go clean、go run、go fmt、go install、go get、go vet)-CSDN博客