open conf/test/conf.yaml: no such file or directory
https://github.com/cloudwego/cwgo/issues/120
https://github.com/cloudwego/cwgo/issues/29
在使用Kitex生成的代码中,单元测试时回报错,如标题所示。出现该错的原因是,biz/service
单元测试方法同样需要完全的框架测试,而不是独立的单元测试,就像spring boot在进行单元测试时,需要加载整个的spring环境。
看到获取conf.yaml
的源码:
func initConf() {
prefix := "conf"
confFileRelPath := filepath.Join(prefix, filepath.Join(GetEnv(), "conf.yaml"))
content, err := ioutil.ReadFile(confFileRelPath)
if err != nil {
panic(err)
}
conf = new(Config)
err = yaml.Unmarshal(content, conf)
if err != nil {
hlog.Error("parse yaml error - %v", err)
panic(err)
}
if err := validator.Validate(conf); err != nil {
hlog.Error("validate config error - %v", err)
panic(err)
}
conf.Env = GetEnv()
pretty.Printf("%+v\n", conf)
}
在上述代码中通过GetEnv()
方法获取yaml配置文件的中间路径,方法体如下:
func GetEnv() string {
e := os.Getenv("GO_ENV")
if len(e) == 0 {
return "test"
}
return e
}
上述方法是获取go env的目录与本项目无关,需要修改。显然这里是需要启动环境的,如
dev
,online
,test
等。这个在另一篇文章在将,这里先把项目跑起来。
GetEnv()
就先写一个test
也先在该目录下conf文件写配置
那么按照上述配置完后应该变成如下所示:
prefix := "conf"
confFileRelPath := filepath.Join(prefix, filepath.Join("test", "conf.yaml"))
}
调用该方法时合成的路径就是conf/test/conf.yaml
所以显然是找不到的。conf
目录之外的结构都没有。
此时只需要将查找conf.yaml
的位置设置为绝对的路径,这样不管在那个文件下使用,配置文件都是从绝对目录查询,就不会报错找不到了。
go语言提供了os.Getwd()
方法获取绝对路径。
需要注意的是该方法是动态的,不是获取该方法所在目录而是获取调用该方法的文件所在目录。因此该方法是不行的,应为路径随方法调用的位置而改变。
于是使用filepath.Abs()
方法,该方法是获取某个文件的绝对路径。这里就可获取conf的绝对路径在设置conf.yaml
路径。
.
是指当前目录。
但是这样也是行不通的,因为go中获取路径都是动态的,对os.GetWd()
或者filepath.Abs()
的封装都是形式上的,只有在实际赋值时才会真正调用。如下
在测试环境调用变为测试环境地址
主函数调用变为主函数地址
那么该如何解决呢?
小编是对路径解析直到获取根路径,在通过根路径配置配置文件路径,这样不管在那个目录调用都先获取根路径再配置配置文件路径。
小编的想法是:
- 获取conf调用者的绝对路径
os.Getwd()
- 由于go无法获取整个项目的绝对路径,那么就需要对路径解析获取项目绝对路径
- 使用
filepath.Dir(currentDir)
获取上一级目录- 使用
filepath.Base(currentDir)
获取3
的最后一个目录的名称,并与项目根目录比较- 相等就说明该路径为绝对路径
- 通过
1,2,3,4,5
不论在任何目录调用,不论绝对地址如何变化,都可以获取项目根目录- 根目录(项目名的比较可以从配置文件,或者从main函数得到,不要想我一些写死在代码中) 【可选】
那么修改后的conf的initConf
代码如下:
func initConf() {
// 主函数为最外层函数,路径为根地址,其它路径调用只可能在其子目录下
// 获取调用者路径
fakepath,err := os.Getwd()
if err != nil{
log.Fatal(errors.New("获取调用者路径失败"))
panic(err)
}
// 循环比较路径的最后一位是否为根目录
// 如果是根目录就停止,循环超20次自动停止(没有目录有这么多层)
i := 0
for {
i++
if filepath.Base(fakepath) == "food_platform" {
// 这里的food_platform是最原始根目录路径名,也就是项目最外层文件名
break
}
fakepath = filepath.Dir(fakepath)
if i >= 20 {
log.Fatal(errors.New("获取调用者路径失败"))
panic(err)
}
}
prefix := fakepath + "/cms_commodity/api/conf"
// 获取配置文件路径
confFileRelPath := filepath.Join(prefix, filepath.Join("test","conf.yaml"))
content, err := ioutil.ReadFile(confFileRelPath)
if err != nil {
panic(err)
}
conf = new(Config)
err = yaml.Unmarshal(content, conf)
if err != nil {
klog.Error("parse yaml error - %v", err)
panic(err)
}
if err := validator.Validate(conf); err != nil {
klog.Error("validate config error - %v", err)
panic(err)
}
conf.Env = GetEnv()
pretty.Printf("%+v\n", conf)
}
上述有三处需要结合实际修改的地方:
/test
这里写的静态的,可以使用os.Args
和flag
库从命令行中获取。- if判断的
food_platform
条件就是项目名,顶级根项目的名,这个需要结合实际修改,当然也可以写在配置文件中,或从命令行获取。/cms_commodity/api/conf
这个是写实在代码中的,是从根目录到配置文件之间的路径,这样就要求项目的结构不能改动。