Go项目如何让相对路径同时兼容go run与go test运行场景?
问题原因
Go 执行go test命令时,每个包的测试进程的工作目录是该包自身的源码目录,而非项目根目录。你当前写死的相对路径是相对于项目根目录的,测试运行时自然找不到对应文件,同时你在init()函数中自动执行文件读取逻辑,会在包被导入时立刻触发读取,没有机会动态修改路径。
可行解决方案
方案1:使用go:embed内嵌密钥文件(推荐,无需处理路径问题)
Go 1.16及以上版本内置embed标准库,可以直接将静态资源编译到二进制文件中,完全不需要处理文件路径问题,同时适配go run、go test、Docker部署所有场景。
修改security/token/generator.go代码如下:
package security import ( "crypto/ecdsa" _ "embed" "log" "github.com/golang-jwt/jwt" ) // 直接内嵌私钥文件,路径为当前go文件相对于私钥文件的相对路径 //go:embed ../../data/keys/512/private-key.pem var privateKeyBytes []byte var privateKey *ecdsa.PrivateKey func init() { var err error privateKey, err = jwt.ParseECPrivateKeyFromPEM(privateKeyBytes) if err != nil { log.Fatalf("Unable to parse ECDSA private key: %v", err) } }
优点:完全不需要处理路径兼容问题,部署的时候只需要传二进制文件即可,不需要额外携带密钥文件。
注意:如果你的密钥会经常更换,不适合内嵌的话,可以选择下面的方案。
方案2:显式初始化+配置化路径
改掉init()自动读取的逻辑,改成显式调用的初始化函数,路径通过配置传入:
- 修改
security/token/generator.go代码:
package security import ( "crypto/ecdsa" "io/ioutil" "log" "github.com/golang-jwt/jwt" ) var privateKey *ecdsa.PrivateKey // InitTokenGenerator 显式初始化,外部传入私钥路径 func InitTokenGenerator(privateKeyPath string) { data, err := ioutil.ReadFile(privateKeyPath) if err != nil { log.Fatalf("Can not read private key file: %v", err) } ecdsaKey, err := jwt.ParseECPrivateKeyFromPEM(data) if err != nil { log.Fatalf("Unable to parse ECDSA private key: %v", err) } privateKey = ecdsaKey }
- 主程序
main.go中,启动时调用初始化,传入相对项目根的路径:
func main() { security.InitTokenGenerator("data/keys/512/private-key.pem") // 其他启动逻辑 }
- 测试代码中,调用初始化时传入相对于测试包目录的路径,比如
handler/auth_test.go中:
func init() { // 测试文件在handler目录下,相对路径往上走一层到项目根再找密钥 security.InitTokenGenerator("../data/keys/512/private-key.pem") }
优点:路径灵活可控,适合密钥需要外部配置、不打进二进制的场景,Docker部署时只需要把路径配置成容器内的密钥路径即可。
方案3:动态定位项目根目录
写一个工具函数,自动向上查找go.mod文件所在的目录作为项目根目录,自动拼接绝对路径:
// GetProjectRoot 自动获取项目根目录(go.mod所在目录) func GetProjectRoot() (string, error) { dir, err := os.Getwd() if err != nil { return "", err } for { if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { return dir, nil } parent := filepath.Dir(dir) if parent == dir { return "", fmt.Errorf("go.mod not found") } dir = parent } }
然后读取密钥的时候拼接路径:
root, _ := GetProjectRoot() privateKeyPath = filepath.Join(root, "data/keys/512/private-key.pem")
优点:不需要修改测试代码,自动适配所有运行场景。
缺点:部署的时候需要保证容器内运行目录下存在go.mod文件,或者需要额外判断生产环境的路径。
内容的提问来源于stack exchange,提问作者Muhammed Ozdogan
相关产品推荐
相关产品推荐

