在工作中,经常会遇到这样的情况:就是去确认一个函数,或者一个模块的结果是否正确,比如:
- func AddUpdate(n int) int {
- res := 0
- for i := 1; i <= n; i++ {
- res += i
- }
- return res
- }
在main函数中,调用AddUpdate函数,看看实际输出的结果是否和预期的结果一致,如果一致,则说明函数正确,否则函数有错误,然后修改错误
(1).不方便:
我们需要在 main 函数中去调用,这样就需要去修改 main 函数,如果现在顶目正在运行,就可能去停止顶目
(2).不利于管理:
因为当我们测试多个函数或者多个模块时,都需要写在main函数中,不利于管理和清晰我们的思路
(3)引出单元测试: testing测试框架可以很好解决问题
go语言中自带有一个轻量级的测试框架testing和自带的go test 命令来实现单元测试和性能测试, testing 框架和其他语言中的测试框架类似,可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例。
通过单元侧试,可以解决如下问题:
(1).确保每个函数是可运行,并且运行结果是正确的
(2).确保写出来的代码性能是好的
(3).单元测试能及时的发现程序设计或实现的逻辑错误,使问题及早暴露,便于问题的定位解决,而性能测试的重点在于发现程序设计上的一些问题,让程序能够在高并发的情况下还能保持稳定
使用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
(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 即可
案例要求
(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