当前位置: 首页 > article >正文

goland单元测试

一、单元测试的概念


1.1 什么是单元测试,有什么用?

单元测试是针对于函数的测试,用来保证该函数的逻辑正确性。

1.2 单元测试的要求?


1. 单元测试在正式上线之前应该全部自动执行,并且需要保证全部通过
2. 单元测试需要构建 【输入】 和 【预期输出】的case,case需要靠人工构建,涵盖各种边界情况
3. 单元测试需要测试到代码的每一个逻辑分支
4. 单元测试关注的是代码逻辑是否正确,无需关注网络调用、数据查询等,对于此部分代码可以mock掉
5. 单元测试行覆盖率要达到70%,函数覆盖率要达到100%(controller层除外)

1.3 单元测试的常见误区


1. 单元测试行覆盖率100%只能保证写的测试用例走过了所有的代码,但不能保证代码逻辑完全无误

1.4 gomock工具-gomonkey


官方文档 :https://github.com/agiledragon/gomonkey
使用教程:https://cloud.tencent.com/developer/article/1872029
https://www.ddhigh.com/2021/09/18/gomonkey-private-method-stub/ 解决了无法打桩私有方法的问题

二、单元测试实战

func GetFinalStatus(productName string) (status string, r ocommon.ResultInfo) {
	daoLockPtr := dao.CreateProductLockInfoPtr()
	query := oquery.NewQueryStructOfTable()
	var allLockList []dao.ProductLockInfo
	query.AddConditonsByOperator("ProductName", oquery.OP_EQUAL, productName)
	r = daoLockPtr.SearchByQuery(&allLockList, query)
	if !r.IsOk() {
		return
	}
	// 如果产品线没有查到锁记录,则改产品线锁状态为【未锁定】
	if len(allLockList) == 0 {
		status = LOCK_STATUS_UNLOCK
		return
	}
	// 遍历所有的锁信息
	for _, lockItem := range allLockList {
		// 一旦发现硬锁定,则立即返回产品线锁状态为【硬锁定】
		if lockItem.LockStatus == LOCK_STATUS_HARDLOCK {
			status = LOCK_STATUS_HARDLOCK
			return
		}
	}
	// 遍历完也不存在硬锁定,那就一定是【软锁定】
	status = LOCK_STATUS_SOFTLOCK
	return
}
快速生成单元测试代码

编写测试用例
参考1.2 中的第三点,我们需要涵盖到每一个分支,所以该单元测试至少要有下面三个测试用例

单元测试用例用例一:测试软锁定用例二:测试硬锁定用例三:测试未锁定
输入SIODSIODSIOD
输出软锁定        硬锁定未锁定

mock数据并完成测试代码
参数1.2 中的第四点,该方法的单元测试关注的是代码逻辑是否正确,对于数据的来源不关注,再加上1.2 中的第一点,单元测试需要在每次提交代码之前,全部执行通过
所以我们需要mock掉 daoLockPtr.SearchByQuery 方法,并且将allLockList 变量用mock数据替代
如果不mock掉daoLockPtr.SearchByQuery,而是每次都去从数据库中查询,不可能同时通过上述三个测试用例
此时就用到了上面提到的测试工具,对daoLockPtr.SearchByQuery(&allLockList, query) 方法进行打桩 并mock掉 allLockList数据

具体实现如下方代码
func TestGetFinalStatus(t *testing.T) {
	type args struct {
		productName string
	}
	tests := []struct {
		name       string
		args       args
		wantStatus string
		wantR      ocommon.ResultInfo
		mockData   []dao.LockInfo // 此变量存储每一个测试用例的mock数据
	}{
		// TODO: Add test cases.
		{
			name:       "未锁定",
			args:       args{productName: "SIOD"},
			wantStatus: "unLock",
			wantR:      ocommon.ResultInfo{ErrNo: 0},
			mockData: nil, // 对于未锁定,mockData就是nil
		},
		{
			name:       "软锁定",
			args:       args{productName: "SIOD"},
			wantStatus: "softLock",
			wantR:      ocommon.ResultInfo{ErrNo: 0},
			mockData:   []dao.LockInfo{dao.LockInfo{LockStatus: LOCK_STATUS_SOFTLOCK}},// 对于软锁定,mockData的一种情况就是一条锁的状态为 【软锁】
		},
		{
			name:       "硬锁定",
			args:       args{productName: "SIOD"},
			wantStatus: "hardLock",
			wantR:      ocommon.ResultInfo{ErrNo: 0},
			mockData:   []dao.LockInfo{dao.LockInfo{LockStatus: LOCK_STATUS_HARDLOCK}},// 对于硬锁定,mockData的一种情况就是一条锁的状态为 【硬锁】
		},
	}
	for _, tt := range tests {
		// 使用 gomonkey 来 mock 方法
		patches := gomonkey.ApplyMethod(reflect.TypeOf(&dbBase.DbBase{}), "SearchByQuery", func(_ *dbBase.DbBase, allLockList interface{}, query *oquery.QueryStructOfTable) ocommon.ResultInfo {
			if data, ok := allLockList.(*[]dao.LockInfo); ok {
				*data = tt.mockData
			}
			return ocommon.ResultInfo{}
		})
		t.Run(tt.name, func(t *testing.T) {
			gotStatus, gotR := GetFinalStatus(tt.args.productName)
			if gotStatus != tt.wantStatus {
				t.Errorf("GetFinalStatus() gotStatus = %v, want %v", gotStatus, tt.wantStatus)
			}
			if !reflect.DeepEqual(gotR.ErrNo, tt.wantR.ErrNo) {
				t.Errorf("GetFinalStatus() gotR = %v, want %v", gotR.ErrNo, tt.wantR.ErrNo)
			}
		})
		// 每一个测试用例结束后,将mock的数据清除,不影响下一次mock
		patches.Reset()
	}
}

查看单元测试覆盖率
我们在该方法所在的目录下面执行下面的命令,然后将单元测试的信息输出到目录下的c.out
执行单元测试

GOOS=darwin GOARCH=amd64 go test -cover -coverprofile=c.out -gcflags="all=-N -l" -run 'TestGetFinalStatus'


查看单元测试覆盖率,执行完之后,会自动打开一个html网页

go tool cover -html=c.out


从下图中,可以看出,我们在lock_info.go文件的单元测试覆盖率只有10.3%,我们需要把该文件中的其他方法根据项目实际情况补充单元测试


该方法中只有一行代码没有覆盖到,就是数据库查询错误,单元测试的目的是确保后续的代码逻辑正确,所以这一行代码可以不做测试

如何写出更加完备的单元测试?
上面代码可以看到对于该方法的覆盖已经达到每一个分支(数据库错误处理分支除外),但是其并不能保证其代码逻辑一定没有问题。
所以我们需要再补充几个单元测试用例,正如1.2 中的第二条一样,单元测试的难点就是需要人工去构思各种边界情况

{
	name:    "混合锁取最高级别",
	args:    args{productName: "SIOD"},
	wantStatus: "hardLock",
	wantR:   ocommon.ResultInfo{ErrNo: 0},
	mockData:  []dao.ProductLockInfo{{LockStatus: LOCK_STATUS_HARDLOCK}, {LockStatus: LOCK_STATUS_SOFTLOCK}}, // 此处的mockData 是一个硬锁,一个软锁
},
{
	name:    "多把相同状态的锁",
	args:    args{productName: "SIOD"},
	wantStatus: "hardLock",
	wantR:   ocommon.ResultInfo{ErrNo: 0},
	mockData:  []dao.ProductLockInfo{{LockStatus: LOCK_STATUS_HARDLOCK}, {LockStatus: LOCK_STATUS_HARDLOCK}}, // 此处的mockData 是一个硬锁,一个软锁
},


参考文章
https://www.liwenzhou.com/posts/Go/unit-test-0/#c-0-1-8
https://time.geekbang.org/column/article/10275?utm_campaign=geektime_search&utm_content=geektime_search&utm_medium=geektime_search&utm_source=geektime_search&utm_term=geektime_search


http://www.kler.cn/a/402522.html

相关文章:

  • java charAt()返回数值型 详解
  • 在英文科技论文中分号后面的单词首字母需不需要大写
  • 【TEST】Apache JMeter + Influxdb + Grafana
  • 深入解析 EasyExcel 组件原理与应用
  • ES6 模块化语法
  • PML和金属边界区别
  • 【虚拟机】VMWare的CentOS虚拟机断电或强制关机出现问题
  • 一次成功尝试:旧电脑通过网线,连接带无线网卡电脑上外网
  • Android和IOS的区别
  • C++——智能指针剖析
  • 专家PID控制
  • 在 for 循环中,JVM可能会将 arr.length 提升到循环外部,仅计算一次。可能会将如何解释 详解
  • AwsCredentialsProvider认证接口
  • Python运算符列表
  • C++设计模式之适配器模式与桥接模式,装饰器模式及代理模式相似点与不同点
  • 数据结构 【带环单链表】
  • 【Chatgpt】如何通过分层Prompt生成更加细致的图文内容
  • Android开发实战班 - 数据持久化 - Room 数据库应用
  • Spark Catalyst 优化器具有高度的可扩展性,如何自定义优化规则?
  • Zero-Shot Next-Item Recommendation using Large Pretrained Language Models-问题咨询
  • 阿里云 DevOps 资源安全扫描实践
  • nginx配置不缓存资源
  • Spark SQL大数据分析快速上手-完全分布模式安装
  • LLM( Large Language Models)典型应用介绍 1 -ChatGPT Large language models
  • 3_Flink CDC
  • Eagle-OJ 开源的在线编程训练平台