[go学习笔记.第十三章.单元测试] 1.单元测试

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

[上一节][go学习笔记.第十二章.文件操作] 2.json基本介绍

[下一节][go学习笔记.第十四章.协程和管道] 1.协程的引入,调度模型以及运行cpu数目,协程资源竞争问题 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值