惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
C
CXSECURITY Database RSS Feed - CXSecurity.com
V
Visual Studio Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
小众软件
小众软件
L
LangChain Blog
Cyberwarzone
Cyberwarzone
美团技术团队
The Register - Security
The Register - Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tor Project blog
V
V2EX
Security Archives - TechRepublic
Security Archives - TechRepublic
Hacker News: Ask HN
Hacker News: Ask HN
L
LINUX DO - 最新话题
Recent Announcements
Recent Announcements
H
Hackread – Cybersecurity News, Data Breaches, AI and More
酷 壳 – CoolShell
酷 壳 – CoolShell
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
aimingoo的专栏
aimingoo的专栏
人人都是产品经理
人人都是产品经理
F
Full Disclosure
V2EX - 技术
V2EX - 技术
The Cloudflare Blog
博客园 - 叶小钗
T
Threat Research - Cisco Blogs
阮一峰的网络日志
阮一峰的网络日志
G
GRAHAM CLULEY
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Latest news
Latest news
S
Security @ Cisco Blogs
Spread Privacy
Spread Privacy
Project Zero
Project Zero
K
Kaspersky official blog
MyScale Blog
MyScale Blog
Attack and Defense Labs
Attack and Defense Labs
云风的 BLOG
云风的 BLOG
博客园 - 【当耐特】
Hacker News - Newest:
Hacker News - Newest: "LLM"
大猫的无限游戏
大猫的无限游戏
P
Privacy International News Feed
Google DeepMind News
Google DeepMind News
WordPress大学
WordPress大学
C
Cybersecurity and Infrastructure Security Agency CISA
Webroot Blog
Webroot Blog
罗磊的独立博客
Vercel News
Vercel News
N
News and Events Feed by Topic
A
Arctic Wolf
C
CERT Recently Published Vulnerability Notes

Posts on WKLKEN THINKING

apisix 中的 lrucache apisix 中的服务发现机制 apisix 中的负载均衡 apisix etcd机制 聊聊框架 关于 k8s 的 zero downtime deployment 一些建议 apisix 遇到的一些问题 关于在除夕前一天换了一个洗衣机的故事 Django DRF 性能优化 DRF 的一些实践 Part1: Serializer DRF继承关系图 Better Code: 关于接口的灵活性 新的仓库: wklken/naming 缓存使用的一些经验 Better Code: 抽象: 可扩展性与可维护性的抉择 Better Code: 异常时, 该提示用户哪些信息? Better Code: 更好的异常日志打印 Go: some libs Go: go-redis/cache升级的坑 Go: logrus性能提升 Go: gin validation 远程办公的一点总结 Go: 开发过程中的一些bug 项目管理实践: 风险驱动开发 Go: 一种error wrap调用链处理方式 漫谈技术选型 Go: 基于 apitest 做handler层单元测试 Go: go-sql-driver interpolateparams参数优化 [分享]深度工作 你需要更多的思考时间 Django项目重构小结 工作七年小结: 学习,生活及其他 [分享]bash日常: bash-utils 极客时间推广海报 2017总结: 予时光以意义 k8s APIServer源码: api注册详细细节 k8s APIServer源码: api注册主体流程 k8s APIServer源码: 服务启动 k8s APIServer源码: go-restful框架 重构 - 读书笔记(Python示例) 写给新人的沟通建议 vim 杂谈 - 关于快速编辑 vim 杂谈 - 关于移动 读书笔记-重构: 章11 处理概括关系 读书笔记-重构: 章10 简化函数调用 读书笔记-重构: 章9 简化表达式 读书笔记-重构: 章8 重新组织数据 读书笔记-重构: 章7 在对象之间搬移特性 读书笔记-重构: 章6 重新组织函数 Python 代码规范小结 [分享]关于vim ElasticSearch集群部署文档 Logstash+ElasticSearch处理mysql慢查询日志 [分享]关于代码调试DE那些事 Logstash+ElasticSearch+Kibana- 实现相对通用的数据收集分析 ELK维护的一些点(二) [分享]Python源码剖析-数据结构 一些Centos Python生产环境的部署命令 摘录<<6个月学会任何一种外语>> ELK 维护的一些点 也许是一个新的开始 一些vim的个性化配置 读书笔记-调试九法 这段时间的一些想法 Python 源码阅读 - 垃圾回收机制 我为什么要写博客 APUE笔记-第一章 UNIX基础知识 Python源码阅读-闭包的实现 Python源码阅读-内存管理机制(二) Python源码阅读-内存管理机制(一) Python-基础-数据结构小结 '活动'设计的一些trick 一些简单的Python测试题 我的tmux配置及说明【k-tmux】 Review and Restart 工作四周年小结 vim插件: surround & repeat[成对符号编辑] vim插件: gundo[时光机] vim插件: expand-region[区域选中] vim插件: quickrun[快速执行] vim插件: trailing-whitespace[行尾空格处理] vim插件: closetag[成对标签补全] vim插件: ctrlp[文件搜索] vim插件: airline[状态栏增强] vim插件: theme[主题] vim插件: tagbar[大纲式导航] vim插件: nerdcommenter[快速注释] vim插件: rainbow_parentheses[括号高亮] vim插件: syntastic[语法检查] vim插件: delimitmate[符号自动补全] vim插件: matchit[成对标签跳转] vim插件: easy-align[快速对齐] vim插件: multiple-cursors[多光标操作] vim插件: vim-signature[快速标记跳转] vim插件: easymotion[快速跳转] vim插件: vundle[管理插件] Elasticsearch几个问题的解决 分享一份 Vim 简介PPT k-vim 更新9.0版本 关于知识管理工具的思考
Go: 基于 Ginkgo 框架进行单元测试实践
2021-01-22 · via Posts on WKLKEN THINKING

这是在做 Golang 项目中的一些实践

ginkgo 基于BDD的测试框架; 开始前, 需要花半个小时阅读官方文档

reference

table-driven

一个函数

// TruncateString truncate string to specific length
func TruncateString(s string, n int) string {
	if n > len(s) {
		return s
	}
	return s[:n]
}

使用table-driven方式生成测试代码

func TestTruncateString(t *testing.T) {
	type args struct {
		s string
		n int
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := util.TruncateString(tt.args.s, tt.args.n); got != tt.want {
				t.Errorf("TruncateString() = %v, want %v", got, tt.want)
			}
		})
	}
}

如果是简单的util函数, 用例比较简单并且用例数量较少, 此时单元测试可读性和可维护性没有问题

table-driven的问题

当涉及业务相关的单元测试, 此时

  1. 用例复杂, 输入可能是多层嵌套的struct, 某一层的某个变量的值影响输出
  2. 用例数量多, 某个函数可能有5个以上的用例

由于table-driven的表达能力有限:

  1. 如何复用输入结构体? (当前情况开发会复制粘贴过去改)
  2. name过于简单不被重视, 导致单元测试失败时难以快速阅读

最终带来的问题是:

  1. 单个用例构造复杂, 可能是十几行甚至是几十行;
  2. 用例和用例之间没有复用, 基本基于复制粘贴;
  3. 难以区分用例之间的差异
  4. 用例过多导致难以维护, 不能明确知道每个用例的目的, 用例和用例之间的差别;
  5. 单个测试集过大, 可能有几百行测试代码;

难以阅读/难以理解/难以维护

优势:

  • 丰富的表达能力
  • 复用: 减少了大量重复代码, 复用
  • 让注意力更集中, 只关注每个用例的差异点
  • 减少了认知负担, 每个case有明确的描述/上下文/差异
  • 更好维护

注意, 实践中:

  • 使用 DescribeContext描述/表示行为和逻辑, 以及层次结构
  • 使用 It 描述及放置每一个用例
  • 利用BeforeEach构建每个用例(It)用到的公共内容(setup); 利用AfterEach执行回收动作(teardown). 此时BeforeEach用到闭包, 构建闭包变量/类型声明等, 在每个It中可以复用到
  • 使用testify 替换ginkgo的Matcher Using Other Matcher Libraries
      1. 减少目前迁移切换的工作量
      1. testify语法更加简洁直接, ginkgo matcher语法略啰嗦多敲好多字

请先阅读官方文档之后, 再开始阅读以下内容

1. 基于ginkgo的table-driven

使用场景: util类的简单测试, input和expect都比较简单

使用

package util_test

import (
	"fmt"
	"strconv"
	"testing"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/ginkgo/extensions/table"
	"github.com/stretchr/testify/assert"

	"example/pkg/util"
)

var _ = Describe("String", func() {

	Describe("TruncateBytes", func() {
		var s = []byte("helloworld")

		DescribeTable("TruncateBytes cases", func(expected []byte, truncatedSize int) {
			assert.Equal(GinkgoT(), expected, util.TruncateBytes(s, truncatedSize))
		},
			Entry("truncated size less than real size", []byte("he"), 2),
			Entry("truncated size equals to real size", s, 10),
			Entry("truncated size greater than real size", s, 20),
		)
	})
})

2. 基于闭包/BeforeEach/It的复用

使用场景: 复杂的输入

Extracting Common Setup: BeforeEach

简单复用

		Describe("WithoutResourceType", func() {
			var a types.Action
			BeforeEach(func() {
				a = types.NewAction()
			})

			It("true", func() {
				assert.True(GinkgoT(), a.WithoutResourceType())
			})

			It("true, empty ResourceType", func() {
				a.Attribute.SetResourceTypes([]types.ActionResourceType{})
				assert.True(GinkgoT(), a.WithoutResourceType())
			})

			It("false", func() {
				a.Attribute.SetResourceTypes([]types.ActionResourceType{
					{
						System: "test",
						Type:   "test",
					},
				})
				assert.False(GinkgoT(), a.WithoutResourceType())
			})

		}

复杂的复用

	Describe("GetPolicesAttrKeys", func() {
		var resource *types.Resource
		var policies []types.AuthPolicy
		BeforeEach(func() {
			resource = &types.Resource{
				System:    "test",
				Type:      "host",
				ID:        "1",
				Attribute: nil,
			}
			policies = []types.AuthPolicy{}
		})

		It("fail", func() {
			errExpr := `[{"system": "test", "type": "host", "expression":
{"OR": {"content": [{"NotExists": {"id": []}}]}}}]`
			policies = []types.AuthPolicy{
				{
					Expression: errExpr,
				},
			}
			_, err := GetPolicesAttrKeys(resource, policies)
			assert.Error(GinkgoT(), err)
		})
})

with patch

	Describe("FillSubjectDetail", func() {
		var r *request.Request
		var ctl *gomock.Controller
		var patches *gomonkey.Patches
		BeforeEach(func() {
			ctl = gomock.NewController(GinkgoT())
			r = request.NewRequest()
		})
		AfterEach(func() {
			ctl.Finish()
			if patches != nil {
				patches.Reset()
			}
		})

		It("pip.GetSubjectPK fail", func() {
			patches = gomonkey.ApplyFunc(pip.GetSubjectPK, func(_type, id string) (pk int64, err error) {
				return -1, errors.New("get subject_pk fail")
			})
			err := r.FillSubjectDetail()
			assert.Error(GinkgoT(), err)
			assert.Contains(GinkgoT(), err.Error(), "get subject_pk fail")

		})
})

ginkgo cli

ginkgo cli文档

bootstrap & generate

# 进入模块
$ cd pkg/util

# 生成一个模块的 {module_name}_suite_test.go
$ ginkgo bootstrap

# 生成模块下某个文件 {file_name}_test.go
$ ginkgo generate set

run

# 运行当前目录下的
$ ginkgo

# 带tags和gcflags (如果做了mock)
$ ginkgo -tags=jsoniter -gcflags=all=-l

# 递归执行当前目录及所有子目录下的
$ ginkgo -r -v -cover

# 只执行某几个目录
$ ginkgo  -r pkg/util pkg/cache

确认覆盖率 make test && make cov

# in Makefile
test:
	go test -mod=vendor -gcflags=all=-l $(shell go list ./... | grep -v mock | grep -v docs) -covermode=count -coverprofile .coverage.cov

cov:
	go tool cover -html=.coverage.cov