扫码订阅《 》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go友会
  • 城市
  • 校园
Go学院
  • Go小课
  • Go小考
  • Go实战
  • 精品课
Go求职
  • 求职辅导🔥
  • Offer收割社群
  • 企业题库
  • 面试宝典
Go宝典
  • 在线宝典
  • B站精选
  • 推荐图书
  • 每日博文
Go仓库
实验区
  • Go周边
  • Go下载
  • Go月刊
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
author-avatar

GOLANG ROADMAP


首页
Go友会
  • 城市
  • 校园
Go学院
  • Go小课
  • Go小考
  • Go实战
  • 精品课
Go求职
  • 求职辅导🔥
  • Offer收割社群
  • 企业题库
  • 面试宝典
Go宝典
  • 在线宝典
  • B站精选
  • 推荐图书
  • 每日博文
Go仓库
实验区
  • Go周边
  • Go下载
  • Go月刊
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
  • 尚硅谷Go语言核心编程教程

    • 课程说明
    • 第1章 GOLANG 开山篇
    • 第2章 GOLANG 的概述
    • 第3章 GOLANG 变量
    • 第4章 运算符
    • 第5章 程序流程控制
    • 第6章 函数、包和错误处理
    • 第7章 数组与切片
    • 第8章 排序和查找
    • 第9章 map
    • 第10章 面向对象编程 ( 上 )
    • 第11章 面向对象编程 ( 下 )
    • 第12章 项目1:家庭收支记账软件项目
    • 第13章 项目2:客户信息关系系统
    • 第14章 文件操作
    • 第15章 单元测试
    • 第16章 goroutine和channel
    • 第17章 反射
    • 第18章 TCP 编程
    • 第19章 REDIS 的使用
    • 第20章 数据结构

扫码订阅《 》或入驻星球,即可阅读文章!

第15章 单元测试


GOLANG ROADMAP

# 15.1 先看一个需求

【点击观看视频】单元测试的引出

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

如:

//一个被测试函数
func addUpper(n int)  int {
	res := 0
	for i := 1; i <= n - 1; i++ {
		res += i
	}
	return res
}
1
2
3
4
5
6
7
8

# 15.2 传统的方法

# 15.2.1 传统的方式来进行测试

在main函数中,调用addUpper函数,看看实际输出的结果是否和预期的结果一致,如果一致,则说明函数正确,否则函数有错误,然后修改错误

代码实现:

package main

import (
	"fmt"
	_ "fmt"
)

//一个被测试函数
func addUpper(n int) int {
	res := 0
	for i := 1; i <= n-1; i++ {
		res += i
	}
	return res
}

func main() {
	//传统的测试方法,就是在main函数中使用看看结果是否正确
	res := addUpper(10) // 1.+ 10 = 55
	if res != 55 {
		fmt.Printf("addUpper错误 返回值=%v 期望值=%v\n", res, 55)
	} else {
		fmt.Printf("addUpper正确 返回值=%v 期望值=%v\n", res, 55)
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 15.2.2 传统方法的缺点分析

1 ) 不方便, 我们需要在main函数中去调用,这样就需要去修改main函数,如果现在项目正在运行,就可能去停止项目。

2 ) 不利于管理,因为当我们测试多个函数或者多个模块时,都需要写在main函数,不利于我们管理和清晰我们思路

3 ) 引出单元测试。->testing 测试框架 可以很好解决问题。

# 15.3 单元测试-基本介绍

Go语言中自带有一个轻量级的测试框架testing和自带的gotest命令来实现单元测试和性能测试,testing框架和其他语言中的测试框架类似,可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例。通过单元测试,可以解决如下问题:

1 ) 确保每个函数是可运行,并且运行结果是正确的

2 ) 确保写出来的代码性能是好的,

3 ) 单元测试能及时的发现程序设计或实现的逻辑错误,使问题及早暴露,便于问题的定位解决,而性能测试的重点在于发现程序设计上的一些问题,让程序能够在高并发的情况下还能保持稳定

# 15.4 单元测试-快速入门

【点击观看视频】单元测试快速入门

使用Go的单元测试,对addUpper和sub函数进行测试。

cal.go

package cal

//一个被测试函数
func addUpper(n int)  int {
	res := 0
	for i := 1; i <= n - 1; i++ {
		res += i
	}
	return res
}

//求两个数的查
func getSub(n1 int, n2 int) int {
	return n1 - n2
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

cal_test.go

package cal
import (
	"fmt"
	"testing" //引入go 的testing框架包
)

//编写要给测试用例,去测试addUpper是否正确
func TestAddUpper(t *testing.T) {

	//调用
	res := addUpper(10)
	if res != 55 {
		//fmt.Printf("AddUpper(10) 执行错误,期望值=%v 实际值=%v\n", 55, res)
		t.Fatalf("AddUpper(10) 执行错误,期望值=%v 实际值=%v\n", 55, res)
	}

	//如果正确,输出日志
	t.Logf("AddUpper(10) 执行正确...")

}

func TestHello(t *testing.T) {

	fmt.Println("TestHello被调用..")

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

特别说明 : 测试时,可能需要暂时退出 360 。(因为 360 可能会认为生成的测试用例程序是木马)

演示如何进行单元测试:

单元测试的运行原理示意图:

image-20210118195904988

# 15.4.1 单元测试快速入门总结

【点击观看视频】单元测试细节说明

1 ) 测试用例文件名必须以 _test.go 结尾。 比如 cal_test.go,cal 不是固定的。

2 ) 测试用例函数必须以Test开头,一般来说就是Test+被测试的函数名,比如TestAddUpper

3 ) TestAddUpper(t*tesingT) 的形参类型必须是 *testingT 【看一下手册】

4 ) 一个测试用例文件中,可以有多个测试用例函数,比如 TestAddUpper、TestSub

5 ) 运行测试用例指令

  • ( 1 )cmd>gotest [如果运行正确,无日志,错误时,会输出日志]
  • ( 2 )cmd>gotest-v [运行正确或是错误,都输出日志]

6 ) 当出现错误时,可以使用tFatalf 来格式化输出错误信息,并退出程序

7 ) tLogf 方法可以输出相应的日志

8 ) 测试用例函数,并没有放在main函数中,也执行了,这就是测试用例的方便之处[原理图]

9 ) PASS表示测试用例运行成功,FAIL 表示测试用例运行失败

10 ) 测试单个文件,一定要带上被测试的原文件 gotest-v cal_testgocalgo

11 )测试单个方法 gotest-v-testrun TestAddUpper

# 15.5 单元测试-综合案例

【点击观看视频】单元测试综合案例

image-20210118200032535

代码实现:

monster/monster.go

package monster
import (
	"encoding/json"
	"io/ioutil"
	"fmt"
)

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.Println("marshal err =", err)
		return false
	} 

	//保存到文件
	filePath := "d:/monster.ser"
	err = ioutil.WriteFile(filePath, data, 0666)
	if err != nil {
		fmt.Println("write file err =", err)
		return false
	}
	return true
}


//给Monster绑定方法ReStore, 可以将一个序列化的Monster,从文件中读取,
//并反序列化为Monster对象,检查反序列化,名字正确
func (this *Monster) ReStore() bool {

	//1. 先从文件中,读取序列化的字符串
	filePath := "d:/monster.ser"
	data, err := ioutil.ReadFile(filePath)
	if err != nil {
		fmt.Println("ReadFile err =", err)
		return false
	}

	//2.使用读取到data []byte ,对反序列化
	err = json.Unmarshal(data, this)
	if err != nil {
		fmt.Println("UnMarshal err =", err)
		return false
	}
	return true
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

monster/monster.go

package monster

import (
	"testing"
)

//测试用例,测试 Store 方法
func TestStore(t *testing.T) {

	//先创建一个Monster 实例
	monster := &Monster{
		Name : "红孩儿",
		Age :10,
		Skill : "吐火.",
	}
	res := monster.Store()
	if !res {
		t.Fatalf("monster.Store() 错误,希望为=%v 实际为=%v", true, res)
	}
	t.Logf("monster.Store() 测试成功!")
}

func TestReStore(t *testing.T) {

	//测试数据是很多,测试很多次,才确定函数,模块..
	
	//先创建一个 Monster 实例 , 不需要指定字段的值
	var monster = &Monster{}
	res := monster.ReStore() 
	if !res {
		t.Fatalf("monster.ReStore() 错误,希望为=%v 实际为=%v", true, res)
	}

	//进一步判断
	if monster.Name != "红孩儿" {
		t.Fatalf("monster.ReStore() 错误,希望为=%v 实际为=%v", "红孩儿", monster.Name)
	}

	t.Logf("monster.ReStore() 测试成功!") 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  • 15.1 先看一个需求
  • 15.2 传统的方法
  • 15.2.1 传统的方式来进行测试
  • 15.2.2 传统方法的缺点分析
  • 15.3 单元测试-基本介绍
  • 15.4 单元测试-快速入门
  • 15.4.1 单元测试快速入门总结
  • 15.5 单元测试-综合案例