Go語言入坑記錄

原因

本來主要使用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語言基本編程以外,主要任務是要找到常用功能包。

語言特性相關

  1. struct ,Go沒有類,struct相當於類。struct的方法也是一個函數,但是需要在func的關鍵字後增加一個參數定義,這個參數稱爲“接收者”,有點類似於Python的self。但是這個接收者有兩種可選形式,一個是傳值,一個是傳引用,如果需要改變調用者本身,則需要傳引用。
  2. map ,Go沒有象Python那麼靈活的字典類型,對應的是map,而且需要事先確定類型。當類型不能確定,或者是任意類型的時候,就需要使用到interface ,在處理json文件的時候會遇到。
  3. 異常處理 ,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語言特性,找到了合適的功能模塊,目標實際一點,還是能實現的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章