实际开发中,需要保证单元功能正确。
传统方式:在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)
}