实际开发中,需要保证单元功能正确。
传统方式:在main函数中直接调用,查看结合是否和预期一致。
缺点:1. 不方便 2. 不利于管理
因此,单元测试具有必要性
testing测试框架
Go语言中自带testing轻量级测试框架和go test命令来实现单元测试和性能测试。可以解决以下问题:
1. 确保每个函数可运行并且结果正确
2. 确保单元代码的性能
3. 尽早发现错误
入门实例以及解析
需求:在service包下有一个函数addUpper需要测试。
解决方式:在service包下创建一个xxx_test.go文件,创建一个测试用例,代码如下:
package service
import(
_ "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 main
import (
"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 Monster
m.ReStore("MonsterJson.json")
fmt.Println(m)
}
_test.go文件代码:
package main
import (
"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 Monster
err = 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)
}