go 有没有什么优雅的办法可以进行单元测试?

pkoukk:刚刚入坑 golang 没多久,用 go 写了一个小的项目,逐渐感受到了 go 的特性和优点
为了增加项目的可靠性,想写一点单元测试,但是发现了重重阻碍,主要是在 mock 的时候实在是太复杂了
个人体验上感觉,无法做到无侵入性的 mock 某些 func 或者 struct 。
下面写一下自己的做法,不知道是因为我原本代码结构设计的就不对还是 mock 的姿势不对,希望大家指正

函数 mock 。

在一般的使用中假设是这样的

func BaseFunc() {
	info := getInfo("123")
	fmt.Printf(info)
}

func getInfo(name string) string {
	return name + ".cn"
}

func usage() {
	BaseFunc()
}

如果我需要 mock,就得

type getInfoFunc func(string) string

func BaseWithMock(getInfoV getInfoFunc) {
	info := getInfoV("123")
	fmt.Printf(info)
}

func mockGetInfo(name string) string {
	return name + ".com"
}

func usage() {
    // 正常调用
    BaseWithMock(getInfo)
    // mock
	BaseWithMock(mockGetInfo)
}

那么就存在问题了,如果我的 baseFunc 当中有很多数据库或者 API 接口,在单元测试的时候我需要 mock 他们的数据,
我就必须要定义很多个 type,然后在 BaseFunc 的参数里传进来么?感觉这么做很不优雅。

如果试图去 mock 一个对象,我感觉就更复杂了..
以下是某种简化过的场景..
假设 ServiceRecord 代表一系列数据库和 API 等数据操作
Service 则代表具体处理的对象,那么如果 Service 需要通过 ServiceRecord 读取某些基础信息的场景。
一般情况下,我是这样写的

type ServiceRecord struct {
	Name   string
	Fields map[string]string
}

func (s *ServiceRecord) LoadFields() {
	// some database work
	result := map[string]string{
		"name":    "jack",
		"address": "no.1 jack street",
		"remark":  "none",
	}
	s.Fields = result
}

type Service struct {
	Name  string
	Owner string
}

func (s *Service) ReadMoreInfo() {
	r := &ServiceRecord{Name: s.Name}
	r.LoadFields()
	s.Owner = r.Fields["name"]
}

func usage(serviceName string) {
	s := &Service{Name: serviceName}
	s.ReadMoreInfo()
	fmt.Print(s.Owner)
}

如果需要进行单元测试,我们需要 mock 掉数据层,也就是 ServiceRecord 这个对象。
一般是通过 Interface 来实现这件事情。

type ServiceRecordInterface interface {
    LoadFields()
    // 因为 Interface 本身不包含数据,所以原来的所有直接访问属性的地方,都必须使用函数来实现
	GetField(string) string
}

func (s *ServiceRecord) GetField(fieldName string) string {
	return s.Fields[fieldName]
}

// 为了 Mock,需要通过参数把接口传进来
func (s *Service) ReadMoreInfo(serviceRecord ServiceRecordInterface) {
	serviceRecord.LoadFields()
	s.Owner = serviceRecord.GetField("name")
}

// 正常调用时
func usage(serviceName string) {
	s := &Service{Name: serviceName}
	s.ReadMoreInfoForMock(&ServiceRecord{Name: serviceName})
	fmt.Printf(s.Owner)
}

// mock 对象
type ServiceRecordMock struct {
	ServiceRecord
}

// mock 掉具体的函数实现
func (srm *ServiceRecordMock) LoadFields() {
	result := map[string]string{
		"name":    "tony",
		"address": "no.1 tony street",
		"remark":  "none",
	}
	srm.Fields = result
}

// mock 时
func mockUsage(serviceName string) {
	s := &Service{Name: serviceName}
	s.ReadMoreInfoForMock(&ServiceRecordMock{ServiceRecord{Name: serviceName}})
	fmt.Printf(s.Owner)
}

可以看出这仍然对原来的代码产生了很大的影响,为了满足可 mock,必须要声明一个接口,而且必须把这个接口抽离出来,作为参数注入到调用对象里面去。
让我觉得很尴尬的是,采用接口之后,必须通过函数去 get 或者 set 一个属性,感觉很不优雅,产生了很多没有必要的垃圾代码。

Go 中怎么实现类似 Java 里的枚举类型?

woostundy:用定义常量来实现枚举类型,太简易了。没法通过值找到枚举名称,没法约束值范围,没法输出所有可选枚举值。 试过在自定义类型上面加 String(), All() 方法,但代码又多又丑陋。 有什么好的写法或者第三方包能实现吗?scnace:code generation (逃 lbp0200:直接复制粘贴了type Direction intc…

请问有什么比较火,并且好用的 go 的 job 调度框架吗?

secretName:由于公司技术栈的原因,所以现在打算在 go 里面找一个 job 调度框架。 找了一圈,只发现 gocron 貌似还可以的样子,但是调度只支持 shell 与 http,连异步任务都没有,我这里常用的任务有时候调度时间通常都在好几小时,这样显然是满足不了要求的。 难道只能自己造一个轮子了吗?sirius1024:robfig/cron

string.Format不带小数位会产生意外的舍入 - c#

我需要更新一些现有代码,以便(有条件地)显示数字而没有小数位。根据是否需要“£”符号使用以下两行。currency = string.Format(CultureInfo.CurrentCulture, "{0:£#,0.00}", data); 要么currency = string.Format(CultureInfo.Current…

我可以在Golang中加载经过Python训练的分类器吗? - python

我正在尝试在Golang服务器中加载经过python训练的分类器。在python中,我通常这样做:classifier = pickle.load( open("classifier1.p", "rb")) 在Golang中有等同的功能吗?我花了3天的时间来训练数据,我等不及了。因此,我尝试使用此代码将classifi…

Scrapy-splash-lua_script中的splash:go(url)是否再次执行GET请求? - javascript

我是Scrapy-splash的新手,我正在尝试抓取一个带有AJAX分页的表格的懒惰datatable。因此,我需要加载网站,等待执行JS,获取表格的html,然后在分页中单击“下一步”按钮。我的方法可行,但恐怕我要两次访问该网站。第一次生成SplashRequest时,然后执行lua_script时。是真的吗如果是,如何使其仅执行一次请求?class JS…