单元测试
针对使用传统方式测试代码块的不足:
1)测试代码块需要在main函数中去调用,需要修改main函数,若项目正在运行,就可能去停止项目,不方便
2)不利于管理,当需要测试多个函数或多个模块时,都需要在main函数,不利于我们管理和清晰思路
3)引出单元测试。testing测试框架,很好解决上述问题。
1. 基本介绍
Go语言自带有一个轻量级的测试框架testing和自带的 go test命令实现单元测试和性能测试。testing框架和其它语言中的测试框架类似,可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例。通过单元测试,可以解决:
1)确保每个函数的是可运行,并且运行结果是正确的
2)确保代码的性能是好的
3)单元测试能及时的发现程序设计或实现的逻辑错误,使得问题及时暴露,便于问题的定位和解决;而性能测试的重点在于发现程序设计上的问题,确保程序在高并发的情况下还能保持稳定。
2.单元测试入门案例
Golang中提供了testing
包,该包提供对Go包的自动化测试的支持。通过go test
命令能够自动执行如下形式的任何函数:
func TestXxx(*testing.T)
- 其中 Xxx 可以是任何字母数字字符串(但第一个字母不能是 [a-z],且必须大写),用于识别测试程序。
2.要编写一个新的测试套件,需要创建一个名称以 _test.go 结尾的文件,该文件包含TestXxx
函数,如上所述。 将该文件放在与被测试的包相同的包中。该文件将被排除在正常的程序包之外,但在运 行 “go test” 命令时将被包含。
测试原理图:
入门案例:
使用到testing
包中的func (*T) Logf
与func (*T) Fatalf
。
func (*T) Logf
func (c *T) Logf(format string, args …interface{})
Log 使用与 Printf 相同的格式化语法对它的参数进行格式化,然后将格式化后的文本记录到错误日志里面。
1)对于测试来说,Logf 产生的格式化文本只会在测试失败或者设置了 -test.v 标志的情况下被打印出来;
func (*T) Fatalf
func (c *T) Fatalf(format string, args …interface{})
调用 Fatalf 相当于在调用 Logf 之后调用 FailNow 。
func (*T) FailNow
func (c *T) FailNow()
将当前测试标识为失败并停止执行该测试,在此之后,测试过程将在下一个测试或者下一个基准测试中继续。
案例代码:
被测试函数所在文件cal.go
package cal
func addUpper(num int) int {
res := 0
for i := 1; i <= num; i++ {
res += i
}
return res
}
func GetSub(n1, n2 int) int {
res := n2 - n1//计划是n1-n2 故意写错为n2-n1
return res
}
测试函数cal_test.go
package cal
import "testing"
// 编写测试案案例,测试addUpper是否正确
func TestAddupper(t *testing.T) {
// 调用
res := addUpper(10)
if res != 55 {
t.Fatalf("addUpper(10) 执行错误,期望值=%v,实际值=%v\n", 55, res)
}
// 如果正确,输出日志
t.Logf("addUpper(10) 执行正确...")
}
func TestGetSub(t *testing.T) {
res := GetSub(10, 5)
if res != 5 {
t.Fatalf("GetSub(10,5) 执行错误,期望值=%v,实际值=%v\n", 5, res)
}
t.Logf("GetSub(10,5) 执行正确")
}
运行测试指令:
(1)cmd>go test (如果运行正确,无日志,错误时输出日志)
(2)cmd> go test -v (运行正确或错误都输出日志)
(3)PASS表示测试运行成功,FAIL表示测试失败
(4)测试单个文件,需要带上被测试的原文件:go test -v cal_test.go cal.go
(5)测试单个方法:go test -v -run TestAddUpper
Output:
E:\goproject\src\go_code\chapter14\testdemo01>go test -v
=== RUN TestAddupper
cal_test.go:13: addUpper(10) 执行正确…
— PASS: TestAddupper (0.00s)
=== RUN TestGetSub
cal_test.go:19: GetSub(10,5) 执行错误,期望值=5,实际值=-5
— FAIL: TestGetSub (0.00s)
FAIL
exit status 1
FAIL go_code/chapter14/testdemo01 0.489s
说明cal.go中GetSub()函数有问题
OutPut
E:\goproject\src\go_code\chapter14\testdemo01>go test
— FAIL: TestGetSub (0.00s)
cal_test.go:19: GetSub(10,5) 执行错误,期望值=5,实际值=-5
FAIL
exit status 1
FAIL go_code/chapter14/testdemo01 0.247s
使用go test
命令执行,则出现错误才会显示t.Logf()
案例2:
检测josn序列化和反序列化是否正确。
1)使用ioutil.WriteFile()
函数将json字符串写入文件
func WriteFile(filename string, data []byte, perm os.FileMode) error
函数向filename指定的文件中写入数据。如果文件不存在将按给出的权限创建文件,否则在写入数据之前清空文件。因此使用时需谨慎。
2)使用ioutil.ReadFile()
函数读取文件中的json信息
func ReadFile(filename string) ([]byte, error)
ReadFile 从filename指定的文件中读取数据并返回文件的内容。成功的调用返回的err为nil而非EOF。因为本函数定义为读取整个文件,它不会将读取返回的EOF视为应报告的错误。
操作代码:
被测文件monster.go
package monster
import (
"encoding/json"
"io/ioutil"
"log"
)
type Monster struct {
Name string `json:"name"`
Age int `json:"age,omitempty"`
Skill string `json:"skill,omitempty"`
}
func (m *Monster) Store() error {
data, err := json.Marshal(m)
if err != nil {
log.Fatal(err)
}
// 保存到文件
filePath := "E:\\goproject\\src\\go_code\\chapter14\\monster.ser"
// 将json信息写入文件
ioutil.WriteFile(filePath, data, 0666)
return err // nil
}
func (m *Monster) Restore() error {
// 从文件中获得json信息
filePath := "E:\\goproject\\src\\go_code\\chapter14\\monster.ser"
data, err := ioutil.ReadFile(filePath)
if err != nil {
log.Fatal(err)
}
err = json.Unmarshal(data, m)
if err != nil {
log.Fatal(err)
}
return err
}
测试文件 monster_test.go
package monster
import (
"testing"
)
var monster = Monster{
Name: "红孩儿",
Age: 1500,
Skill: "火尖枪",
}
// 测试用例,测试Store方法
func TestStore(t *testing.T) {
if monster.Store() == nil {
t.Logf("marshal successfully\n")
} else {
t.Logf("marshal fail,err=%v\n", monster.Store())
}
}
// 测试用例,测试Restore方法
func TestRestore(t *testing.T) {
monster1 := &Monster{}
if monster1.Restore() != nil {
t.Logf("unmarshal fail\n")
} else {
if *monster1 == monster {
t.Logf("Unmarshal successfully\n")
} else {
if monster1.Name != monster.Name {
t.Logf("Unmarshal fail,期望%v, 实际是%v\n", monster.Name, monster1.Name)
}
if monster1.Age != monster.Age {
t.Logf("Unmarshal fail,期望%v, 实际是%v\n", monster.Age, monster1.Age)
}
if monster1.Skill != monster.Skill {
t.Logf("Unmarshal fail,期望%v, 实际是%v\n", monster.Skill, monster1.Skill)
}
}
}
}