最近在写Go的项目,使用的框架是Gin,众所周知,Gin是一个比较简单的框架,只提供了核心功能,并没有配置文件模块,所以这块得自己搞了,Go的第三方解析配置的库非常多,无论是ini、yaml、json文件支持都非常好,而且Go的项目一般都是常驻进程的,所以只需要在项目启动的时候解析一次就行可以了。

示例

最简单的办法通常就是定义一个全局的配置变量供其它包使用,在init函数里面初始化加载配置文件,示例如下:

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
package config

import (
"gopkg.in/ini.v1"
"log"
"os"
)

var Conf Config

type Config struct {
App App
}

type App struct {
Port string
Debug string
Url string
LogFile string
}

func init() {
envFile := "app.ini"
conf, err := ini.Load(envFile)
if err != nil {
log.Panicf("parse conf file [%s] failed, err: %s", envFile, err.Error())
}
sectionApp := conf.Section("APP")
Conf.App = App{
Port: sectionApp.Key("PORT").String(),
Debug: sectionApp.Key("DEBUG").String(),
Url: sectionApp.Key("URL").String(),
LogFile: sectionApp.Key("LOG_FILE").String(),
}
log.Println("init config file success")
}

默认情况下,入口文件main.go文件都是位于项目根目录下面,和app.ini文件同级,所以这种写法完全没问题。

问题

但是当你跑测试用例的时候,而且当这个测试用例并不在项目根目录的时候就会产生问题: 找不到配置文件。

原因很简单,Go的测试用例最佳实践是和被测试的文件放在一起,所以测试文件可能在二级、三级甚至多级目录里面,如下图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
├── app.ini
├── config
│   ├── Config.go
│   └── DataBase.go
├── controller
│   ├── BaseController.go
├── lib
│   ├── function
│   │   ├── Aes.go
│   │   ├── Rsa.go
│   │   ├── Rsa_test.go
│   │   └── Uuid.go
│   ├── httpLogger
│   │   └── HttpLogger.go
│   └── zlog
│   ├── SqlLog.go
│   └── ZapLogger.go
├── main.go

所以在测试文件的目录下肯定是找不到app.ini的,咋办呢?解决方法有很多

  • copy一个配置到测试文件。这种方法最简单粗暴,但是太不灵活,测试用例可能在任何目录里面,这样搞有点难受

  • 配置文件路径写成绝对路径。这种方法也不灵活,毕竟每个人的项目目录位置不一样,以后线上部署也麻烦

  • 采用依赖注入的高级写法,测试的时候使用mock的方式注入配置。这种方法可以,也是比较好的方式,但是需要引入依赖注入组件,整个项目的架构需要更改,不推荐使用依赖注入把简单的问题复杂化。

  • 跑测试的时候传入外部参数,依然不够灵活,而且麻烦

这个问题,我思考了很久,最终想了一个足够简单灵活的方式,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
envFile := "app.ini"
// 读取配置文件, 解决跑测试的时候找不到配置文件的问题,最多往上找5层目录
for i := 0; i < 5; i++ {
if _, err := os.Stat(envFile); err == nil {
break
} else {
envFile = "../" + envFile
}
}
conf, err := ini.Load(envFile)
if err != nil {
log.Panicf("parse conf file [%s] failed, err: %s", envFile, err.Error())
}

使用一个for循环解决了这个问题,如果怕不够保险,可以改成10,大多数项目目录应该不会这么深,虽然不够优雅,但是还是相对比较简单的。

各位有什么更好的方法吗?有的话请留言指教