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


    1.引入

     

            在工作中,经常会遇到这样的情况:就是去确认一个函数,或者一个模块的结果是否正确,比如:

    1. func AddUpdate(n int) int {
    2. res := 0
    3. for i := 1; i <= n; i++ {
    4. res += i
    5. }
    6. return res
    7. }

    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:

    1. package main
    2. //一个被测试的函数
    3. func AddUpper(n int) int {
    4. res := 0
    5. for i := 0; i <= n; i++ {
    6. res += i
    7. }
    8. return res
    9. }
    10. func getSub(n1 int, n2 int) int {
    11. return n1 - n2
    12. }

    cal_test.go:

    1. package main
    2. import (
    3. "testing" //引入go的testing测试框架
    4. )
    5. //编写一个测试用例,去厕所addUpper是否正确
    6. func TestAddUpper(t *testing.T) {
    7. //调用
    8. res := AddUpper(10)
    9. if res != 55 {
    10. t.Fatalf("AddUpper错误,返回值=%v,期望值=%v\n", res, 55)
    11. }
    12. //如果正确,输出日志
    13. t.Logf("AddUpper(10)正确..")
    14. }

     sub_test.go:

    1. package main
    2. import (
    3. _ "fmt"
    4. "testing" //引入go的testing测试框架
    5. )
    6. //编写一个测试用例,去厕所addUpper是否正确
    7. func TestGetSub(t *testing.T) {
    8. //调用
    9. res := getSub(10, 3)
    10. if res != 7 {
    11. t.Fatalf("getSub错误,返回值=%v,期望值=%v\n", res, 7)
    12. }
    13. //如果正确,输出日志
    14. t.Logf("getSub(10, 3)正确..")
    15. }

    结果: 

    1. go test -v
    2. === RUN TestAddUpper
    3. cal_test.go:16: AddUpper(10)正确..
    4. --- PASS: TestAddUpper (0.00s)
    5. === RUN TestGetSub
    6. sub_test.go:16: getSub(10, 3)正确..
    7. --- PASS: TestGetSub (0.00s)
    8. PASS
    9. 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开头的测试方法时,案例:

    1. package dao
    2. import (
    3. "fmt"
    4. "go_code/web_app/book/model"
    5. "testing"
    6. "time"
    7. )
    8. func TestOrder(t *testing.T) {
    9. fmt.Println("添加订单测试相关")
    10. //t.Run("测试添加订单", testAddOrder)
    11. //t.Run("测试获取数据库中所有订单", testGetOrders)
    12. //t.Run("通过订单id获取对应的订单项", tesGetOrderItemsByOrderID)
    13. //t.Run("测试获取数据库中用户订单", testGetMyOrder)
    14. t.Run("测试根据订单号更新订单", testUpdateOrderState)
    15. }
    16. //测试添加订单
    17. func testAddOrder(t *testing.T){
    18. //创建订单
    19. timeStr := time.Now().Format("2006-01-02 15:04:05")
    20. order := &model.Order{
    21. OrderID : "1234564",
    22. CreateTime: timeStr,
    23. TotalAmount: 2,
    24. TotalCount: 2,
    25. State: 0,
    26. UserID: 2,
    27. }
    28. //创建订单项
    29. orderItem1:= &model.OrderItem{
    30. Count: 1,
    31. Amount: 1,
    32. Title: "测试",
    33. Author: "罗",
    34. Price: 1,
    35. ImgPath: "/static/img/default.jgp",
    36. OrderID: "1234564",
    37. }
    38. //创建订单项
    39. orderItem2:= &model.OrderItem{
    40. Count: 1,
    41. Amount: 1,
    42. Title: "测试12",
    43. Author: "罗2",
    44. Price: 1,
    45. ImgPath: "/static/img/default.jgp",
    46. OrderID: "1234564",
    47. }
    48. //添加订单以及订单项
    49. err := AddOrder(order)
    50. if err != nil {
    51. fmt.Println("order add test fail,err=", err)
    52. }
    53. err1 := AddOrderItem(orderItem1)
    54. if err != nil {
    55. fmt.Println("order item add test fail,err=", err1)
    56. }
    57. err2 := AddOrderItem(orderItem2)
    58. if err != nil {
    59. fmt.Println("order item add test fail,err=", err2)
    60. }
    61. }
    62. //测试获取数据库中所有订单
    63. func testGetOrders(t *testing.T) {
    64. orders,_ := GetOrders()
    65. for _,v := range orders {
    66. fmt.Println("图书:", v)
    67. }
    68. }
    69. //测试获取数据库中用户订单
    70. func testGetMyOrder(t *testing.T) {
    71. orders,_ := GetMyOrder(2)
    72. for _,v := range orders {
    73. fmt.Println("图书:", v)
    74. }
    75. }
    76. //测试通过订单id获取对应的订单项
    77. func tesGetOrderItemsByOrderID(t *testing.T) {
    78. order_items,_ := GetOrderItemsByOrderID("c341c646-6eab-4b74-771e-8dc5c9c1cbce")
    79. for _,v := range order_items {
    80. fmt.Println("图书对应的订单项:", v)
    81. }
    82. }
    83. //测试根据订单号更新订单
    84. func testUpdateOrderState(t *testing.T) {
    85. err := UpdateOrderState("cf040684-d392-4c3e-5216-43777683f917", 2)
    86. if err != nil {
    87. fmt.Println(err)
    88. }
    89. }

    运行测试代码 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 

    1. package monster
    2. import (
    3. "fmt"
    4. "encoding/json"
    5. "io/ioutil"
    6. )
    7. type Monster struct {
    8. Name string
    9. Age int
    10. Skill string
    11. }
    12. //给Monster绑定方法Store,可以将一个monster变量(对象)序列化后保存到文件
    13. func (this *Monster) Store() bool {
    14. //直接将序列化后,保存
    15. data, err := json.Marshal(this)
    16. if err != nil {
    17. fmt.Printf("marshal err = %v\n", err)
    18. return false
    19. }
    20. //保存到文件
    21. filePath := "f:/www/monster.ser"
    22. err = ioutil.WriteFile(filePath, data, 0666)
    23. if err != nil {
    24. fmt.Printf("write file err = %v\n", err)
    25. return false
    26. }
    27. return true
    28. }
    29. //给Monster绑定方法ResStore,可以将一个序列化的monster,从文件中读取,
    30. //并反序列化成Monster对象,检查反序列化,名字是否正确
    31. func (this *Monster) ResStore() bool {
    32. //先从文件中读出文件
    33. filePath := "f:/www/monster.ser"
    34. data, err := ioutil.ReadFile(filePath)
    35. if err != nil {
    36. fmt.Printf("read file err = %v\n", err)
    37. return false
    38. }
    39. //反序列化
    40. err = json.Unmarshal(data, this)
    41. if err != nil {
    42. fmt.Printf("unmarshal file err = %v\n", err)
    43. return false
    44. }
    45. return true
    46. }

    monster_test.go 

    1. package monster
    2. import(
    3. "testing"
    4. )
    5. func TestStore(t *testing.T) {
    6. //先创建一个Monster
    7. monster := Monster{
    8. Name : "张三",
    9. Age : 12,
    10. Skill : "爬树",
    11. }
    12. res := monster.Store()
    13. if !res {
    14. t.Fatalf("monster store err, 希望为:%v,实际为:%v", true, res)
    15. }
    16. t.Logf("monster store sueccss")
    17. }
    18. func TestResStore(t *testing.T) {
    19. //先创建一个monster实例,不需要指定字段的值
    20. var monster Monster
    21. res := monster.ResStore()
    22. if !res {
    23. t.Fatalf("monster resstore err, 希望为:%v,实际为:%v", true, res)
    24. }
    25. //进一步判断
    26. if monster.Name != "张三" {
    27. t.Fatalf("monster resstore err, 希望为:%v,实际为:%v", monster.Name, res)
    28. }
    29. t.Logf("monster resstore sueccss")
    30. }

     结果:

    1. go test -v
    2. === RUN TestStore
    3. monster_test.go:18: monster store sueccss
    4. --- PASS: TestStore (0.00s)
    5. === RUN TestResStore
    6. monster_test.go:33: monster resstore sueccss
    7. --- PASS: TestResStore (0.00s)
    8. PASS
    9. ok go_code/testcase 0.758s

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

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

  • 相关阅读:
    echarts重叠柱状图(非堆叠柱状图)
    思科防火墙解析(ASA)
    基于图搜索的规划算法之A*家族(五):D* 算法
    算法通关村-----图的基本算法
    03-JS循环语句
    C#学习笔记--面向对象三大特征
    Android多线程实现
    tomcat10.1.0源码分析
    vue实现keep-alive页面缓存【三步骤配置,一步到位】
    Otsu阈值分割详解
  • 原文地址:https://blog.csdn.net/zhoupenghui168/article/details/127740293