🔖
Go初心者必見!テストも安心、どこからでも設定ファイルを正しく読み込む裏ワザ
なぜ設定ファイルが読み込めなくなるの?
ディレクトリと変更前のコード
ディレクトリは以下の通り
├─Root
│ ├─main.go
│ ├─config.ini
│ ├─config
│ │ ├─config.go <---このconfig.goにconfig.iniを読み込むコードを書いています
│ ├─test
│ │ ├─config_test.go
↓変更前のコードは以下の通り
config.go
func LoadConfig(section string)(*Config, error){
cfg, err := ini.Load("config.ini") //<---これが今回問題の箇所
if err != nil{
return nil, fmt.Errorf("iniファイル読込エラー:%w", err)
}
return &Config {
Logfile: cfg.Section(section).Key("logfile").String(),
User: cfg.Section(section).Key("user").String(),
Password: cfg.Section(section).Key("password").String(),
DBname: cfg.Section(section).Key("dbname").String(),
}, nil
}
問題となる場合
main.goからconfig.goを実行すると、main.goとconfig.iniはカレントディレクトリに居るので、
cfg, err := ini.Load("config.ini")
は問題なく実行できる。
ただ、テスト等の時に「カレントディレクトリ(現在の作業ディレクトリ)」が変わります
そのため、
ini.Load("config.ini") のようにカレントディレクトリからの相対パスで設定ファイルを指定していると、
「ファイルが見つからない!」というエラーが発生しがちです。
でも、、、テストのたびに書き換えるの面倒じゃーん
という事で、以下の手法をご提案します。
対処法
config.goから見て、どこにconfig.iniがいるかを指してPathを通す
config.goから見て、config.iniがどこにいるか探す関数を作成
config.go
func getProjectRoot() string {
_, filename, _, _ := runtime.Caller(0)
return filepath.Dir(filepath.Dir(filename))
}
説明
-
runtime.Caller(0)
- 現在の関数(
getProjectRoot
)が呼ばれた場所のファイルパスを取得します。 - 第1引数(
0
)は「呼び出し元のスタックフレームの深さ」を表し、0
は「自分自身」を指します。
- 現在の関数(
-
filename
- この関数が書かれているファイルのフルパスが格納されます。
- 今回はconfig.goで呼ばれてるから、root/config/config.goと取得できる
-
filepath.Dir(filename)
-
filename
の親ディレクトリ(=この関数が書かれたファイルのあるディレクトリ)を取得します。
-
-
filepath.Dir(filepath.Dir(filename))
- 上記、
filename
の親ディレクトリ(=この関数が書かれたファイルのあるディレクトリ)のさらに親ディレクトリ(=プロジェクトルート)ということ。 - 今回は、config.goから見たconfig.ini場所を取得したい(config.goの親ディレクトリ=config--->のさらに親ディレクトリ=Root)
- 上記、
config.iniがどこにいるか探す関数をiniファイルの読み込みに使用
LoadConfig()の中で、config.goから見たconfig.iniの絶対pathを指定して読込実行
config.go
gofunc LoadConfig() {
root := getProjectRoot()
cfgpath := filepath.Join(root, "config.ini")
cfg, err := ini.Load(cfgpath)
*// (以下、エラー処理や設定の利用などが続く)*
}
説明
-
root := getProjectRoot()
- プロジェクトルートのパスを取得します。
-
cfgpath := filepath.Join(root, "config.ini")
- プロジェクトルート直下の
config.ini
ファイルのパスを組み立てます。
- プロジェクトルート直下の
-
cfg, err := ini.Load(cfgpath)
-
config.ini
ファイルを読み込み、cfg
に設定情報が格納されます。 -
ini.Load
はgithub.com/go-ini/ini
パッケージの関数です。
-
-
(エラー処理など)
- 実際にはここで
err
をチェックし、エラー処理を行うことが多いです。
- 実際にはここで
3. まとめ
-
getProjectRoot()
- プロジェクトルートのパスを取得するための関数。
-
LoadConfig()
- プロジェクトルート直下の
config.ini
を読み込む関数。
- プロジェクトルート直下の
-
この方法を使うと、どこから実行しても
config.ini
を確実に読み込める- テストやコマンド実行時のカレントディレクトリの違いに強くなります。
絶対Path直書きではなぜダメなの?
結局上記の説明って、絶対PathをLoadConfig()に渡してるよね?
であれば、
例えば、絶対Pathが"/Root/config/config.ini"の場合、
では、そもそも、
func LoadConfig(section string)(*Config, error){
cfg, err := ini.Load("/Root/config/config.ini")
と直書きすればよかったのでは?
Discussion