1.引入
在工作中,经常会遇到这样的情况:就是去确认一个函数,或者一个模块的结果是否正确,比如:
func AddUpdate(n int) int {
res := 0
for i := 1; i <= n; i++ {
res += i
}
return res
}
2.传统的方法
1).传统方法来进行测试
在main函数中,调用AddUpdate函数,看看实际输出的结果是否和预期的结果一致,如果一致,则说明函数正确,否则函数有错误,然后修改错误
2).传统方法的缺点分析
(1).不方便:
我们需要在 main 函数中去调用,这样就需要去修改 main 函数,如果现在顶目正在运行,就可能去停止顶目
(2).不利于管理:
因为当我们测试多个函数或者多个模块时,都需要写在main函数中,不利于管理和清晰我们的思路
(3)引出单元测试: testing测试框架可以很好解决问题
3.单元测试基本介绍
go语言中自带有一个轻量级的测试框架testing和自带的go test 命令来实现单元测试和性能测试, testing 框架和其他语言中的测试框架类似,可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例。
通过单元侧试,可以解决如下问题:
(1).确保每个函数是可运行,并且运行结果是正确的
(2).确保写出来的代码性能是好的
(3).单元测试能及时的发现程序设计或实现的逻辑错误,使问题及早暴露,便于问题的定位解决,而性能测试的重点在于发现程序设计上的一些问题,让程序能够在高并发的情况下还能保持稳定
4.快速入门
使用go单元测试,对AddUpdate和 sub 函数进行测试.
特别说明:
测试时,可能需要暂时退出 360.(因为 360 可能会认为生成的测试用例程序是木马)演示如何进行单元测试:
cal.go:
package main
//一个被测试的函数
func AddUpper(n int) int {
res := 0
for i := 0; i <= n; i++ {
res += i
}
return res
}
func getSub(n1 int, n2 int) int {
return n1 - n2
}
cal_test.go:
package main
import (
"testing" //引入go的testing测试框架
)
//编写一个测试用例,去厕所addUpper是否正确
func TestAddUpper(t *testing.T) {
//调用
res := AddUpper(10)
if res != 55 {
t.Fatalf("AddUpper错误,返回值=%v,期望值=%v\n", res, 55)
}
//如果正确,输出日志
t.Logf("AddUpper(10)正确..")
}
sub_test.go:
package main
import (
_ "fmt"
"testing" //引入go的testing测试框架
)
//编写一个测试用例,去厕所addUpper是否正确
func TestGetSub(t *testing.T) {
//调用
res := getSub(10, 3)
if res != 7 {
t.Fatalf("getSub错误,返回值=%v,期望值=%v\n", res, 7)
}
//如果正确,输出日志
t.Logf("getSub(10, 3)正确..")
}
结果:
go test -v
=== RUN TestAddUpper
cal_test.go:16: AddUpper(10)正确..
--- PASS: TestAddUpper (0.00s)
=== RUN TestGetSub
sub_test.go:16: getSub(10, 3)正确..
--- PASS: TestGetSub (0.00s)
PASS
ok go_code/teststringdemo1/testcase 1.060s
5.快速入门总结
(1).测试用例文件名必须以_test.go 结尾。比如cal_test.go,cal不是固定的
(2).测试用例函数必须以Test开头,一般来说就是Test+被测试的函数名,比如 TestAddUpper
(3).TestAddUpper(t *testing.T)的形参类型必须是 *testing.T
(4).一个测试用例文件中,可以有多个测试用例函数,比如TestAddUpdate,TestSub
(5).运行测试用例指令:
1).cmd>go test [如果运行正确,无日志,错误时,会输出日志]
2). cmd>go test -v [运行正确或是错误,都输出日志]
(6 ).当出现错误时,可以使用t.Fatalf 来格式化输出错误信息,并退出程序
(7).t.Logf 方法可以输出相应的日志
(8).测试用例函数,并没有放在 main 函数中,也执行了,这就是测试用例的方便之处
(9). PASS表示测试用例运行成功, FAIL 表示测试用例运行失败
(10).测试单个文件,一定要带上被测试的原文件
go test -v cal_tesst.go cal.go
(11).测试单个方法
1). 当测试文件中有多个以Test开头的测试方法时,使用 go test -v -test.run TestAddUpper
2).当测试文件中只有一个以Test开头的测试方法时,案例:
package dao
import (
"fmt"
"go_code/web_app/book/model"
"testing"
"time"
)
func TestOrder(t *testing.T) {
fmt.Println("添加订单测试相关")
//t.Run("测试添加订单", testAddOrder)
//t.Run("测试获取数据库中所有订单", testGetOrders)
//t.Run("通过订单id获取对应的订单项", tesGetOrderItemsByOrderID)
//t.Run("测试获取数据库中用户订单", testGetMyOrder)
t.Run("测试根据订单号更新订单", testUpdateOrderState)
}
//测试添加订单
func testAddOrder(t *testing.T){
//创建订单
timeStr := time.Now().Format("2006-01-02 15:04:05")
order := &model.Order{
OrderID : "1234564",
CreateTime: timeStr,
TotalAmount: 2,
TotalCount: 2,
State: 0,
UserID: 2,
}
//创建订单项
orderItem1:= &model.OrderItem{
Count: 1,
Amount: 1,
Title: "测试",
Author: "罗",
Price: 1,
ImgPath: "/static/img/default.jgp",
OrderID: "1234564",
}
//创建订单项
orderItem2:= &model.OrderItem{
Count: 1,
Amount: 1,
Title: "测试12",
Author: "罗2",
Price: 1,
ImgPath: "/static/img/default.jgp",
OrderID: "1234564",
}
//添加订单以及订单项
err := AddOrder(order)
if err != nil {
fmt.Println("order add test fail,err=", err)
}
err1 := AddOrderItem(orderItem1)
if err != nil {
fmt.Println("order item add test fail,err=", err1)
}
err2 := AddOrderItem(orderItem2)
if err != nil {
fmt.Println("order item add test fail,err=", err2)
}
}
//测试获取数据库中所有订单
func testGetOrders(t *testing.T) {
orders,_ := GetOrders()
for _,v := range orders {
fmt.Println("图书:", v)
}
}
//测试获取数据库中用户订单
func testGetMyOrder(t *testing.T) {
orders,_ := GetMyOrder(2)
for _,v := range orders {
fmt.Println("图书:", v)
}
}
//测试通过订单id获取对应的订单项
func tesGetOrderItemsByOrderID(t *testing.T) {
order_items,_ := GetOrderItemsByOrderID("c341c646-6eab-4b74-771e-8dc5c9c1cbce")
for _,v := range order_items {
fmt.Println("图书对应的订单项:", v)
}
}
//测试根据订单号更新订单
func testUpdateOrderState(t *testing.T) {
err := UpdateOrderState("cf040684-d392-4c3e-5216-43777683f917", 2)
if err != nil {
fmt.Println(err)
}
}
运行测试代码 go test -v -run TestOrder 即可
6.综合案例
案例要求
(1).编写一个 Monster 结构体,字段 Name , Age , Skill
(2).给 Monster 绑定方法 Store ,可以将一个 Monster变量(对象),序列化后保存到文件中
(3).给 Monster 绑定方法 ReStore ,可以将一个序列化的 Monster ,从文件中读取,并反序列化为 Monster 对象,检查反序列化,名字正确
(4).编程测试用例文件store_test.go ,编写测试用例函数 TestStore和TestRestore 进行测试
monster.go
package monster
import (
"fmt"
"encoding/json"
"io/ioutil"
)
type Monster struct {
Name string
Age int
Skill string
}
//给Monster绑定方法Store,可以将一个monster变量(对象)序列化后保存到文件
func (this *Monster) Store() bool {
//直接将序列化后,保存
data, err := json.Marshal(this)
if err != nil {
fmt.Printf("marshal err = %v\n", err)
return false
}
//保存到文件
filePath := "f:/www/monster.ser"
err = ioutil.WriteFile(filePath, data, 0666)
if err != nil {
fmt.Printf("write file err = %v\n", err)
return false
}
return true
}
//给Monster绑定方法ResStore,可以将一个序列化的monster,从文件中读取,
//并反序列化成Monster对象,检查反序列化,名字是否正确
func (this *Monster) ResStore() bool {
//先从文件中读出文件
filePath := "f:/www/monster.ser"
data, err := ioutil.ReadFile(filePath)
if err != nil {
fmt.Printf("read file err = %v\n", err)
return false
}
//反序列化
err = json.Unmarshal(data, this)
if err != nil {
fmt.Printf("unmarshal file err = %v\n", err)
return false
}
return true
}
monster_test.go
package monster
import(
"testing"
)
func TestStore(t *testing.T) {
//先创建一个Monster
monster := Monster{
Name : "张三",
Age : 12,
Skill : "爬树",
}
res := monster.Store()
if !res {
t.Fatalf("monster store err, 希望为:%v,实际为:%v", true, res)
}
t.Logf("monster store sueccss")
}
func TestResStore(t *testing.T) {
//先创建一个monster实例,不需要指定字段的值
var monster Monster
res := monster.ResStore()
if !res {
t.Fatalf("monster resstore err, 希望为:%v,实际为:%v", true, res)
}
//进一步判断
if monster.Name != "张三" {
t.Fatalf("monster resstore err, 希望为:%v,实际为:%v", monster.Name, res)
}
t.Logf("monster resstore sueccss")
}
结果:
go test -v
=== RUN TestStore
monster_test.go:18: monster store sueccss
--- PASS: TestStore (0.00s)
=== RUN TestResStore
monster_test.go:33: monster resstore sueccss
--- PASS: TestResStore (0.00s)
PASS
ok go_code/testcase 0.758s