實際開發中,需要保證單元功能正確。
傳統方式:在main函數中直接調用,查看結合是否和預期一致。
缺點:1. 不方便 2. 不利于管理?
因此,單元測試具有必要性
testing測試框架
Go語言中自帶testing輕量級測試框架和go test命令來實現單元測試和性能測試。可以解決以下問題:
1. 確保每個函數可運行并且結果正確
2. 確保單元代碼的性能
3. 盡早發現錯誤
入門實例以及解析
需求:在service包下有一個函數addUpper需要測試。
解決方式:在service包下創建一個xxx_test.go文件,創建一個測試用例,代碼如下:
package serviceimport(_ "fmt""testing" //引入testing測試框架
)// 編寫一個測試用例,測試同包下addUpper函數是否正確
func TestAddUpper(t *testing.T){// 調用res := addUpper(10)if res != 55{t.Fatalf("AddUpper執行錯誤,期望值%v,實際值%v", 55, res)}// 如果正確,就輸出日志t.Logf("執行正確...")
}
使用命令行調用testing框架進行測試。
go test:Go 的測試命令,會自動查找當前目錄及其子目錄中所有以 _test.go 結尾的文件,并運行其中的測試函數。
-v(verbose):啟用“詳細輸出”模式。在該模式下,測試過程中會打印出每個測試函數的執行情況(包括測試通過與否、執行順序等),而不是只顯示最終結果。
在 Go 的測試中,使用 t.Log("...") 或 t.Logf("format", args...) 輸出的日志信息,默認只輸出到控制臺(終端),并不會自動寫入文件或其他地方。如果需要寫入.log文件需要指定。
PASS表示運行成功,FAIL表示運行失敗。
1. 測試文件命名必須是?xxx_test.go
2. 測試函數名必須以?Test?開頭且Test后一位必須為大寫,如?TestSomething(t *testing.T)
3. 只有在包目錄中有?_test.go?文件時,go test?才會運行測試4. 所有用于執行單元測試的函數(即以 Test 開頭的函數)都必須使用 t *testing.T 作為唯一的參數
5. 一個xxx_test.go文件中可以有多個測試函數,以測試一個包中不同的單元。
在測試函數中直接調用被測試函數即可,對結果進行判斷,使用t的方法進行輸出。
運行一個_test.go文件
go test自動查找當前目錄及其子目錄中所有以 _test.go 結尾的文件,但是如果想指定 _test.go文件,就在命令行中指定:
go test -v cal_test.go cal.go
go test:Go 的測試命令,用于運行測試。
-v:表示“verbose(詳細輸出)”,會顯示每個測試函數的執行情況。
cal_test.go:測試文件,里面包含以?TestXXX?開頭的測試函數。
cal.go:普通 Go 源文件,通常是你想測試的代碼邏輯所在文件。
運行一個測試用例
go test -v -run ^TestAddUpper$
或者
go test -v -run TestAddUpper
這樣只會運行該包下指定的一個測試用例,?^ 和 $ 是正則表達式符號,表示完全匹配函數名。不加也可以,但建議加上更精確。
單元測試綜合案例
需求
構建結構體Monster,結構體兩個方法Store和ReStore進行序列化和反序列化。
編寫測試用例測試兩個方法。
Monster結構體和方法代碼:
package mainimport ("encoding/json""fmt""os"
)type Monster struct {Name string `json:"name"`Age int `json:"age"`Skill string `json:"skill"`
}func (m *Monster) Store(filename string) error {// 序列化變量并保存到當前目錄data, err := json.Marshal(m)if err != nil {return err}return os.WriteFile(filename, data, 0644)
}func (m *Monster) ReStore(filename string) error {// 反序列化變量并保存到當前目錄data, err := os.ReadFile(filename)if err != nil {return err}return json.Unmarshal(data, m)
}func main() {// m := Monster{// Name: "牛魔王",// Age: 800,// Skill: "魔王拳",// }// m.Store("MonsterJson.json")var m Monsterm.ReStore("MonsterJson.json")fmt.Println(m)
}
_test.go文件代碼:
package mainimport ("os""reflect""testing"
)const testFile string = "monster.json"func TestStore(t *testing.T) {m := &Monster{Name: "Dracula",Age: 500,Skill: "吸血",}err := m.Store(testFile)if err != nil {t.Fatalf("存儲失敗:%v", err)}// 檢查文件是否存在if _, err := os.Stat(testFile); os.IsNotExist(err) {t.Fatal("文件未生成")}// 清理測試文件os.Remove(testFile)
}// TestRestore 測試 Restore 方法
func TestRestore(t *testing.T) {// 準備一個測試文件內容expected := &Monster{Name: "Frankenstein",Age: 200,Skill: "雷電之力",}// 先將預期對象存入文件err := expected.Store(testFile)if err != nil {t.Fatalf("準備測試文件失敗:%v", err)}// 創建一個新的 Monster 實例并恢復數據var restored Monstererr = restored.ReStore(testFile)if err != nil {t.Fatalf("恢復失敗:%v", err)}// 使用reflect.DeepEqual判斷兩個值是否深度一致if !reflect.DeepEqual(expected, &restored) {t.Errorf("期望值 %v,實際值 %v", expected, restored)}// 清理測試文件os.Remove(testFile)
}