原因
本來主要使用Python,但是由於運行環境受限制的原因,依賴包的維護比較麻煩。因此相中Go語言編譯成單一可執行程序的好處。
目標
將現在用Python實現的一些功能,逐步替換爲用Go來實現,編譯成小工具程序,比如,RestFul API接口測試工具。
經過一些最簡單的Hello World式的編程體驗後,選擇Visual Studio Code作爲編輯工具,Go版本1.14,採用go mod 建項目(主要原因是要使用goproxy https://goproxy.io來下載依賴)。
確定基本目標,實現一個基本的RestFul API接口測試工具。該工具使用excel文件定義基本配置和測試用例,輸出junit格式的報告和excel格式的報告,輸出log文件,接受命令行參數(通過命令行參數指定測試用例文件名、日誌文件名、日誌記錄級別等)。因此除了掌握Go語言基本編程以外,主要任務是要找到常用功能包。
語言特性相關
- struct ,Go沒有類,struct相當於類。struct的方法也是一個函數,但是需要在func的關鍵字後增加一個參數定義,這個參數稱爲“接收者”,有點類似於Python的self。但是這個接收者有兩種可選形式,一個是傳值,一個是傳引用,如果需要改變調用者本身,則需要傳引用。
- map ,Go沒有象Python那麼靈活的字典類型,對應的是map,而且需要事先確定類型。當類型不能確定,或者是任意類型的時候,就需要使用到interface ,在處理json文件的時候會遇到。
- 異常處理 ,Go語言中沒有try那一套。它用error來處理可預見的錯誤,用panic來處理不可預見的異常(panic暫時還沒用到)。
Excel文件讀寫
經過一番選擇,採用了github.com/360EntSecGroup-Skylar/excelize模塊,它的官方文檔的位置爲https://xuri.me/excelize/zh-hans/。瞭解OpenFile、GetSheetMap、Rows、Columns、NewSheet、SetColWidth、SetRowHeight、NewStyle、SetCellStyle、SetCellStyle、SetCellValue、SaveAs等方法,然後看看文檔中的例子,就可以完成Excel文件讀寫。在使用與Cell相關的操作時,它採用的是excel的引用方式,如:“A2”,我們用二維數組[i][j]進行索引,需要自己轉換一下。
文件操作
用go標準庫中os模塊的 os.Stat()、 os.IsExist()、 os.Remove()等方法完成相關操作。
json文件讀寫
常用的有json與map的互轉,json與struct的互轉。直接用encoding/json就可以了。主要的兩個方法,一是json字符串轉map:json.Unmarshal(),二是map字符串轉json: json.Marshal。
xml文件讀寫
選擇了一個第三方模塊github.com/tinyhubs/tinydom,這個模塊很小,用起來還是比較方便的。瞭解了NewDocument()、NewProcInst()、NewElement()、InsertEndChild()、SetAttribute()、SaveDocumentToFile()就可以產生和保存一個xml文件了。詳細見https://github.com/tinyhubs/tinydom,這個模塊只有一個源文件,不清楚的地方可以通過閱讀源碼瞭解。
日誌文件讀寫
選擇了一個第三方模塊go.uber.org/zap,這個模塊在網上有很多教程,基本可以直接借鑑。本項目稍作封裝了一下,在log.go中定義全局變量logger和相關方法,在main.go中進行初始化和關閉,在其它模塊中讀可以直接使用。
package tools
import (
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"time"
)
var logger *zap.Logger
func formatEncodeTime(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(fmt.Sprintf("%d%02d%02d_%02d%02d%02d", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()))
}
func FormateLog(args []interface{}) *zap.Logger {
log := logger.With(ToJsonData(args))
return log
}
func Debug(msg string, args ...interface{}) {
FormateLog(args).Sugar().Debugf(msg)
}
func Info(msg string, args ...interface{}) {
FormateLog(args).Sugar().Infof(msg)
}
func Warn(msg string, args ...interface{}) {
FormateLog(args).Sugar().Warnf(msg)
}
func Error(msg string, args ...interface{}) {
FormateLog(args).Sugar().Errorf(msg)
}
func ToJsonData(args []interface{}) zap.Field {
det := make([]string, 0)
if len(args) > 0 {
for _, v := range args {
det = append(det, fmt.Sprintf("%+v", v))
}
}
zap := zap.Any("detail", det)
return zap
}
func InitZapLog(level string, logfile string) {
var L zapcore.Level
switch level {
case "error":
L = zapcore.ErrorLevel
case "info":
L = zapcore.InfoLevel
case "warn":
L = zapcore.WarnLevel
default:
L = zap.DebugLevel
}
cfg := zap.Config{
Level: zap.NewAtomicLevelAt(L),
Development: true,
Encoding: "json",
EncoderConfig: zapcore.EncoderConfig{
TimeKey: "t",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "msg",
StacktraceKey: "trace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: formatEncodeTime,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
},
OutputPaths: []string{logfile },
ErrorOutputPaths: []string{logfile },
InitialFields: map[string]interface{}{
"app": "test",
},
}
var err error
logger, err = cfg.Build()
if err != nil {
panic("log init fail:" + err.Error())
}
}
func GetLogger() *zap.Logger{
return logger
}
http client
使用標準庫的net/http實現RestFul API的調用,代碼片段如下。
func (e *ApiTest) RestApiCall(method string, url string, header map[string]string, body string) (string, string) {
var failed string = "{\"msg\": \"failed\", \"msg_code\": \"0\"}"
var score string = "Pass"
Info("RestApiCall Begin", method +" " + url)
req, err := http.NewRequest(method, url, strings.NewReader(body))
if err != nil {
Error("request", err)
return failed, score
}
for k, v := range header {
req.Header.Set(k, v)
}
clt := http.Client{}
resp, err := clt.Do(req)
if err != nil {
Error("request", err)
return failed, score
}
defer resp.Body.Close()
result, err := ioutil.ReadAll(resp.Body)
if err != nil {
Error("reponse", err)
return failed, score
}
Info("RestApiCall End", method +" " + url)
return string(result), score
}
命令行參數
使用flag模塊,代碼片段如下。
var setfile= flag.String("set", "setting.xlsx", "配置文件")
var tcfile = flag.String("tc", "testcase.xlsx", "測試用例")
var junit = flag.String("junit", "junit.xml", "junit報告")
var xlsx = flag.String("xlsx", "report.xlsx", "Excel報告")
var logf = flag.String("logf", "iftest.log", "log file")
var logl = flag.String("logl", "debug", "log level")
flag.Parse()
小結
雖然過程中遇到了不少問題,事後小結,只要熟悉了Go語言特性,找到了合適的功能模塊,目標實際一點,還是能實現的。