单元测试
针对使用传统方式测试代码块的不足: 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
return res
}
测试函数cal_test.go
package cal
import "testing"
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 -test.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"
ioutil.WriteFile(filePath, data, 0666)
return err
}
func (m *Monster) Restore() error {
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: "火尖枪",
}
func TestStore(t *testing.T) {
if monster.Store() == nil {
t.Logf("marshal successfully\n")
} else {
t.Logf("marshal fail,err=%v\n", monster.Store())
}
}
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)
}
}
}
}
|