
本文详解 go 测试中因路径拼接错误与目录权限不足导致 `os.isnotexist` 误判及 “not a directory” panic 的根本原因,并提供符合 go 最佳实践的修复方案。
在 Go 单元测试中,使用 osext.Executable() 获取二进制路径后动态构建临时目录(如 /json/)进行文件读写,是常见但易出错的做法。您遇到的问题——os.Stat() 返回非 nil 错误却未触发 os.IsNotExist,随后 os.Mkdir() 失败并报 "not a directory"——本质上源于两个关键缺陷:
❌ 错误一:路径拼接逻辑错误
path.Dir(exePath + "/json/") 是严重错误。exePath 已是完整可执行文件路径(例如 /tmp/go-build123/_test/myio.test),直接在其末尾拼接 "/json/" 后再调用 path.Dir(),会导致路径被错误截断。
✅ 正确做法是:先分离可执行文件的目录部分,再安全拼接子路径:
import "path/filepath" exePath, _ := osext.Executable() dirPath := filepath.Dir(exePath) // 获取可执行文件所在目录 filePath := filepath.Join(dirPath, "json") // 安全拼接子目录
? 更简洁的方式是直接使用 osext.ExecutableFolder()(返回目录路径),避免手动解析。
❌ 错误二:目录权限设置不当
os.Mkdir(filePath, 0644) 创建的目录缺少 执行权限(x-bit)。在 Unix-like 系统中,目录必须具有 x 权限才能被进入(cd)或遍历(openat/stat)。0644(即 -rw-r--r--)仅允许读写,不支持遍历,因此后续 ioutil.WriteFile() 尝试写入 filePath+"/user.txt" 时,内核会拒绝访问并返回 *os.PathError,错误信息为 "not a directory" —— 这并非指路径指向文件,而是指当前进程无权将该路径视为有效目录来访问其内部。
✅ 正确权限应为 0755(drwxr-xr-x),确保所有者可读写执行,组及其他用户可读执行:
if os.IsNotExist(errx) {
if err := os.Mkdir(filePath, 0755); err != nil {
panic(fmt.Sprintf("failed to create dir %s: %v", filePath, err))
}
}✅ 完整修复后的函数示例
package myio
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"bitbucket.org/kardianos/osext"
)
func CreateTempJsonFile() {
// ✅ 正确获取可执行文件所在目录
exePath, _ := osext.Executable()
dirPath := filepath.Dir(exePath)
jsonDir := filepath.Join(dirPath, "json")
fmt.Println("create dir:", jsonDir)
// ✅ 检查并创建目录(含正确权限)
if _, err := os.Stat(jsonDir); os.IsNotExist(err) {
if err := os.Mkdir(jsonDir, 0755); err != nil {
panic(fmt.Sprintf("mkdir failed: %v", err))
}
}
// ✅ 构建完整文件路径并写入
filePath := filepath.Join(jsonDir, "user.txt")
if err := ioutil.WriteFile(filePath, []byte(`{"user": "Mac"}`), 0644); err != nil {
fmt.Println("Error write:", err)
return
}
fmt.Println("Ok write")
// ✅ 读取验证
data, err := ioutil.ReadFile(filePath)
if err != nil {
fmt.Println("Error reading file:", err)
return
}
fmt.Println("Reading file OK, content:", string(data))
}⚠️ 额外建议(提升健壮性)
- 避免 ioutil(已弃用):Go 1.16+ 推荐使用 os.WriteFile / os.ReadFile 替代 ioutil.WriteFile / ioutil.ReadFile。
- 显式错误处理:不要忽略 osext.Executable() 的 error;测试中应使用 t.TempDir() 创建真正隔离的临时目录,而非依赖可执行路径(更可靠、跨平台、无需权限担忧)。
- 清理资源:若需持久化测试目录,应在 defer os.RemoveAll(jsonDir) 中清理;单元测试更推荐 t.TempDir() 自动管理生命周期。
遵循以上修正,即可彻底解决路径误判与权限导致的 "not a directory" panic,让测试文件操作稳定可靠。










