使用Go語(yǔ)言開(kāi)發(fā)自動(dòng)化API測(cè)試工具詳解
前言
上一篇文章說(shuō)到我還開(kāi)發(fā)了一個(gè)獨(dú)立的自動(dòng)測(cè)試工具,可以根據(jù) OpenAPI 的文檔來(lái)測(cè)試,并且在測(cè)試完成后輸出測(cè)試報(bào)告,報(bào)告內(nèi)容包括每個(gè)接口是否測(cè)試通過(guò)和響應(yīng)時(shí)間等。
這個(gè)工具我使用了 go 語(yǔ)言開(kāi)發(fā),主要是考慮到了 go 語(yǔ)言可以傻瓜式的實(shí)現(xiàn)交叉編譯,生成的可執(zhí)行文件直接上傳到服務(wù)器就可以執(zhí)行,非常方便。
PS: go 語(yǔ)言寫起來(lái)是真的折磨!感覺(jué)語(yǔ)法有很多別扭的地方,不過(guò) build 的時(shí)候?qū)嵲谔?,根本無(wú)法拒絕
為了避免篇幅太長(zhǎng),本文先介紹用到的組件,詳細(xì)實(shí)現(xiàn)以及解析 OpenAPI 文檔生成測(cè)試配置的部分后續(xù)的文章再介紹。
網(wǎng)絡(luò)請(qǐng)求
標(biāo)準(zhǔn)庫(kù)中的 net/http
包提供了發(fā)送 HTTP 請(qǐng)求的功能,拿到數(shù)據(jù)之后,使用 json.Unmarshal
函數(shù)解析 JSON 數(shù)據(jù)。這個(gè)包相對(duì)比較低級(jí),對(duì)于簡(jiǎn)單的網(wǎng)絡(luò)請(qǐng)求,夠用,不過(guò)我還是想選擇更好用的組件。
Resty 是一個(gè)簡(jiǎn)單而強(qiáng)大的 Go HTTP 客戶端,具有鏈?zhǔn)?API,可以輕松地發(fā)送 HTTP 請(qǐng)求并處理 JSON 數(shù)據(jù)。它提供了豐富的功能,包括自動(dòng)重試、超時(shí)設(shè)置、請(qǐng)求和響應(yīng)日志等。您可以使用 Resty 來(lái)發(fā)送 GET、POST、PUT、DELETE 等各種類型的請(qǐng)求,并且它能夠自動(dòng)將響應(yīng)的 JSON 數(shù)據(jù)解析為 Go 結(jié)構(gòu)體。
現(xiàn)在出了 v2 版本,支持 HTTP/2、WebSocket、Cookie 操作,并提供了更加簡(jiǎn)潔和易用的 API 。
項(xiàng)目地址: https://github.com/go-resty/resty
使用起來(lái)還行
GET 方法
import "github.com/go-resty/resty/v2" req := c.RestyClient.R().SetHeader("Authorization", "token "+c.AuthToken) req.SetQueryParams(map[string]string{ "year": "2024", }) resp, err = req.Get("path")
POST 方法
req.SetBody(map[string]string{ "year": "2024", }) resp, err = req.Get("path")
SetBody
的參數(shù)是 interface{}
類型,可以傳入的類型比較豐富,我這里還是跟 GET 一樣傳了字典,實(shí)際上應(yīng)該傳 struct
比較多一些吧。
日志組件
我之前用的是 go 語(yǔ)言內(nèi)置的 log ,但似乎功能很少,也沒(méi)有日志等級(jí)啥的,這能叫日志庫(kù)嗎……
接著我找到了在 GitHub 上 star 很多的 logrus 庫(kù),不過(guò)感覺(jué)這是一個(gè)比較古老的庫(kù)了,不太好用,formatter 也沒(méi)找到好用的,看項(xiàng)目主頁(yè)的介紹發(fā)現(xiàn)這個(gè)庫(kù)已經(jīng)進(jìn)入退休狀態(tài)…
它讓我 Check out, for example, Zerolog, Zap, and Apex.
Logrus is in maintenance-mode. We will not be introducing new features. It's simply too hard to do in a way that won't break many people's projects, which is the last thing you want from your Logging library (again...).
項(xiàng)目地址: https://github.com/sirupsen/logrus
所以,最終還是用了 uber 的日志庫(kù) go.uber.org/zap
logrus 使用 & 配置
雖然后面換了 zap ,還是記錄一下關(guān)于 logrus 的使用。
項(xiàng)目主頁(yè)上列舉的幾個(gè)第三方 formatter 我基本都試用了,就這個(gè) nested-logrus-formatter
比較好用。
以下配置實(shí)現(xiàn)了同時(shí)輸出日志到控制臺(tái)和文件。
import ( nested "github.com/antonfisher/nested-logrus-formatter" "github.com/sirupsen/logrus" "os" ) func initLogger() *os.File { logger.SetLevel(logrus.DebugLevel) logger.SetReportCaller(true) logger.SetFormatter(&nested.Formatter{}) // 創(chuàng)建一個(gè)文件作為日志輸出 file, err := os.OpenFile("logfile.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { logger.Fatalf("無(wú)法打開(kāi)日志文件: %v", err) } // 創(chuàng)建一個(gè)多寫入器,將日志同時(shí)輸出到控制臺(tái)和文件 mw := io.MultiWriter(os.Stdout, file) // 添加 Hook 到 Logger 中 logger.Out = mw return file } func main() { file := initLogger() defer func(file *os.File) { err := file.Close() if err != nil { fmt.Println(err) } }(file) }
zap 使用 & 配置
zap 比起 logrus 好用多了,開(kāi)箱即用,搭配 zapcore 可以配置多個(gè)輸出,也可以設(shè)置按日志大小分割文件,還可以對(duì)接其他日志收集平臺(tái)啥的,基本做到了現(xiàn)代日志組件的水平了…
一樣是實(shí)現(xiàn)了同時(shí)輸出日志到控制臺(tái)和文件。
import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" "os" ) func buildLogger() *zap.SugaredLogger { config := zap.NewProductionEncoderConfig() config.EncodeTime = zapcore.ISO8601TimeEncoder consoleEncoder := zapcore.NewConsoleEncoder(config) fileEncoder := zapcore.NewJSONEncoder(config) logFile, _ := os.OpenFile("./log-test-zap.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 06666) tee := zapcore.NewTee( zapcore.NewCore(fileEncoder, zapcore.AddSync(logFile), zap.DebugLevel), zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), zap.DebugLevel), ) var zapLogging = zap.New( tee, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel), ) var logger = zapLogging.Sugar() return logger } func main() { logger := buildLogger() defer logger.Sync() }
swagger 組件
這里我試著用了一下 go swagger
項(xiàng)目地址: https://github.com/go-swagger/go-swagger
這個(gè)可以使用 scoop 安裝
scoop install go-swagger
用著一般,沒(méi)有 swagger code generator 好用。
就沒(méi)繼續(xù)探索下去了。
PS: Jetbrains 系的 IDE 里有幾成 swagger code generator 工具,功能非常強(qiáng)大,可以生成各種代碼。
不過(guò)我還是自己實(shí)現(xiàn)了OpenAPI 文檔解析(比較靈活),所以暫時(shí)還沒(méi)用上這個(gè)強(qiáng)大的工具。
Excel 導(dǎo)出
Excel 操作我用的是這個(gè) github.com/xuri/excelize/v2
一開(kāi)始沒(méi)注意,后面發(fā)現(xiàn)這個(gè)居然是奇安信開(kāi)源的…… 然后看了 qax-os 這個(gè) group ,發(fā)現(xiàn)開(kāi)源的幾個(gè)項(xiàng)目都是跟安全無(wú)關(guān)的,不務(wù)正業(yè)啊老哥!
沒(méi)有對(duì)比其他的,看著 star 挺多,上手就直接用了
感覺(jué)還行。
func exportTestReportsToExcel(testReports []*tester.Report, filename string) error { // 創(chuàng)建一個(gè)新的 Excel 文件 f := excelize.NewFile() // 創(chuàng)建一個(gè)名為 "測(cè)試報(bào)告" 的工作表 index, err := f.NewSheet("測(cè)試報(bào)告") if err != nil { return err } // 設(shè)置工作表列名 f.SetCellValue("測(cè)試報(bào)告", "A1", "接口名稱") f.SetCellValue("測(cè)試報(bào)告", "B1", "接口路徑") f.SetCellValue("測(cè)試報(bào)告", "C1", "測(cè)試是否通過(guò)") f.SetCellValue("測(cè)試報(bào)告", "D1", "耗時(shí)(秒)") // 遍歷測(cè)試報(bào)告并在工作表中寫入數(shù)據(jù) for i, report := range testReports { row := i + 2 f.SetCellValue("測(cè)試報(bào)告", fmt.Sprintf("A%d", row), report.ApiName) f.SetCellValue("測(cè)試報(bào)告", fmt.Sprintf("B%d", row), report.ApiPath) f.SetCellValue("測(cè)試報(bào)告", fmt.Sprintf("C%d", row), func() string { if report.IsPassed { return "是" } return "否" }()) f.SetCellValue("測(cè)試報(bào)告", fmt.Sprintf("D%d", row), report.Elapsed.Seconds()) } // 設(shè)置活動(dòng)工作表 f.SetActiveSheet(index) // 將 Excel 文件保存到磁盤 err = f.SaveAs(filename) if err != nil { return err } return nil }
吐槽
三元表達(dá)式
我很想吐槽 go 為啥沒(méi)有三元表達(dá)式,用匿名函數(shù)真的好繁瑣啊?。?/p>
據(jù)說(shuō)是因?yàn)橛X(jué)得三元表達(dá)式可以寫出很多讓人看不懂的騷代碼,所以 go 不打算支持,因噎廢食啊
不過(guò)這難不倒我,可以寫個(gè)函數(shù)來(lái)模擬,而且現(xiàn)在 go 似乎更新了泛型的功能,不用再拿 interface 來(lái)模擬
func If[T any](condition bool, trueVal, falseVal T) T { if condition { return trueVal } return falseVal }
使用的時(shí)候就
result := If[string](report.IsPassed, "成功", "沒(méi)通過(guò)")
支持類型推導(dǎo),所以 [string]
也可以省略了。
這樣前面導(dǎo)出 Excel 的代碼里的匿名函數(shù)就可以改成這樣,簡(jiǎn)潔多了!
f.SetCellValue("測(cè)試報(bào)告", fmt.Sprintf("C%d", row), If(report.IsPassed, "是", "否"))
數(shù)組排序
本來(lái)也不算什么吐槽,屬于是挑刺了,go 的排序沒(méi)那么好用,但也不難用。
用匿名函數(shù)可以實(shí)現(xiàn)按字段排序,這倒是和 C 語(yǔ)言里用函數(shù)指針大同小異,不愧是帶 gc 的 C 語(yǔ)言
測(cè)試報(bào)告的數(shù)據(jù)結(jié)構(gòu)是這樣
// Report 測(cè)試報(bào)告 type Report struct { ApiName string ApiPath string IsPassed bool Elapsed time.Duration Response *ApiResponse }
我想對(duì) []*Report
數(shù)組排序,可以用 sort.Slice
方法
// 按照 Elapsed 屬性排序,從大到小 sort.Slice(testReports, func(i, j int) bool { return testReports[i].Elapsed > testReports[j].Elapsed })
相比之下還是 cs 的 Linq 舒服啊
testReports.Sort((a, b) => a - b);
小結(jié)
就這樣吧,很簡(jiǎn)單的一個(gè)小工具,因?yàn)檫€處在 go 的小白階段,每用一個(gè)新的庫(kù)都會(huì)記錄一下。
參考資料
uber的Go日志庫(kù)zap使用詳解 -Golang日志操作庫(kù)zap的使用詳解
以上就是使用Go語(yǔ)言開(kāi)發(fā)自動(dòng)化API測(cè)試工具詳解的詳細(xì)內(nèi)容,更多關(guān)于Go自動(dòng)化API測(cè)試工具的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang判斷net.Conn 是否已關(guān)閉的操作
這篇文章主要介紹了golang判斷net.Conn 是否已關(guān)閉的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12基于go interface{}==nil 的幾種坑及原理分析
這篇文章主要介紹了基于go interface{}==nil 的幾種坑及原理分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04go語(yǔ)言reflect.Type?和?reflect.Value?應(yīng)用示例詳解
這篇文章主要為大家介紹了go語(yǔ)言reflect.Type?和?reflect.Value?應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Go1.20最新資訊go?arena手動(dòng)管理內(nèi)存鴿了
由于過(guò)于繁雜,Go?核心團(tuán)隊(duì)成員@Ian?Lance?Taylor,也表態(tài):目前尚未做出任何決定,也不可能在短期內(nèi)做出任何決定,可以認(rèn)為這個(gè)提案基本鴿了,今天這篇文章就是給大家同步目前的情況2023-11-11