简体   繁体   English

Golang:测试和工作目录

[英]Golang: tests and working directory

I'm writing some unit tests for my application in Go. The tests fail however because it cannot find the configuration files.我正在为 Go 中的应用程序编写一些单元测试。但是测试失败了,因为它找不到配置文件。 Normally the binary looks for the configuration files in the working directory under the path conf/*.conf .通常,二进制文件在路径conf/*.conf下的工作目录中查找配置文件。

I figured that browsing to the directory that has conf/ and running go test in it would solve it, but it still reports that the file system cannot find the path specified.我认为浏览到具有conf/的目录并在其中运行go test可以解决它,但它仍然报告文件系统找不到指定的路径。

How can I tell go test to use a certain directory as the working directory so that the tests may actually be executed?我如何告诉go test使用某个目录作为工作目录,以便实际执行测试?

You may be able to use the Caller to get the path to the current test source file, like this:您可以使用 Caller 获取当前测试源文件的路径,如下所示:

package sample

import (
    "testing"
    "runtime"
    "fmt"
)

func TestGetFilename(t *testing.T) {
    _, filename, _, _ := runtime.Caller(0)
    t.Logf("Current test filename: %s", filename)
}

I do not believe this is possible.我不相信这是可能的。 I have not been able to find documentation stating this explicitly, but I believe go test always uses the package directory (containing the go source files) as the working directory.我无法找到明确说明这一点的文档,但我相信go test总是使用包目录(包含 go 源文件)作为工作目录。

As a workaround, I compiled the test and execute the test from the current directory.作为一种解决方法,我编译了测试并从当前目录执行测试。

go test -c && ./<mypackage>.test

Or, if you want a generic command that you can use, you can rename the test file with -o option.或者,如果您想要一个可以使用的通用命令,您可以使用-o选项重命名测试文件。

go test -c -o xyz.test && ./xyz.test

While not really convenient, you can always pass it as a command line variable, for example :虽然不是很方便,但您始终可以将其作为命令行变量传递,例如:

package blah_test

import (
    "flag"
    "fmt"
    "os"
    "testing"
)

var (
    cwd_arg = flag.String("cwd", "", "set cwd")
)

func init() {
    flag.Parse()
    if *cwd_arg != "" {
        if err := os.Chdir(*cwd_arg); err != nil {
            fmt.Println("Chdir error:", err)
        }
    }
}

func TestBlah(t *testing.T) {
    t.Errorf("cwd: %+q", *cwd_arg)
}

Then run it like :然后像这样运行它:

┌─ oneofone@Oa [/tmp]                                                                                             
└──➜ go test . -cwd="$PWD"
--- FAIL: TestBlah (0.00 seconds)
        blah_test.go:16: cwd: "/tmp"

No matter where the work directory is.无论工作目录在哪里。 It must be under your project Dir.它必须在您的项目目录下。 So my solution is所以我的解决方案是

wd, _ := os.Getwd()
for !strings.HasSuffix(wd, "<yourProjectDirName>") {
    wd = filepath.Dir(wd)
}

raw, err := ioutil.ReadFile(fmt.Sprintf("%s/src/conf/conf.dev.json", wd))

Your path should always start from your project Dir.您的路径应始终从您的项目目录开始。 Every time you read the file in a package and accessed by main.go or your another package unit test.每次您读取包中的文件并通过 main.go 或您的另一个包单元测试访问时。 It will always work.它会一直工作。

To add init function into *_test.go under your test package.将 init 函数添加到测试包下的 *_test.go 中。 Test package will run this function before test function start.测试包将在测试功能启动之前运行此功能。

func init() {
    _, filename, _, _ := runtime.Caller(0)
    // The ".." may change depending on you folder structure
    dir := path.Join(path.Dir(filename), "..")
    err := os.Chdir(dir)
    if err != nil {
        panic(err)
    }  
}

You can use the os package .您可以使用os 包

You would want to do something like this你会想做这样的事情

    func TestMyFunction(t *testing.T) {
        os.Chdir("./path")

        //TEST FUNCTION

        os.Chdir("..")
    }

There are several possibilities in the os package. os 包中有几种可能性。

I've had a similar problem and found the solution on this blog我遇到了类似的问题,并在此博客上找到了解决方案

Basically you can change the folder that the test is running using a similar function:基本上,您可以使用类似的功能更改测试正在运行的文件夹:

package main

import (
    "os"
    "path"
    "runtime"
)

func MakeFunctionRunOnRootFolder() {
    _, filename, _, _ := runtime.Caller(0)
    // The ".." may change depending on you folder structure
    dir := path.Join(path.Dir(filename), "..")
    err := os.Chdir(dir)
    if err != nil {
        panic(err)
    }
}

I know this is an old question but I had the same problem trying to use migrations for the database on my tests, and maybe this solution helps someone.我知道这是一个老问题,但我在尝试在测试中使用数据库迁移时遇到了同样的问题,也许这个解决方案可以帮助某人。

Since there is no native way of getting the project directory, you could identify some file or directory that you know it's only in the root of the project (in my case, it was the relative directory database/migrations ).由于没有获取项目目录的本机方式,您可以识别一些您知道它仅位于项目根目录中的文件或目录(在我的情况下,它是相对目录database/migrations )。 Once you have this unique relative directory, you could have a function like the following to obtain the project root directory.一旦你有了这个唯一的相对目录,你就可以有一个像下面这样的函数来获取项目根目录。 It just gets the current working directory (assuming it's inside the project's directory) and starts to navigate all the way up until it finds a dir that has the relative directory you know it's on the root of the project:它只是获取当前的工作目录(假设它在项目目录中)并开始一直向上导航,直到找到一个具有相对目录的目录,您知道它位于项目的根目录:

func FindMyRootDir() string {
    workingDirectory, err := os.Getwd()

    if err != nil {
        panic(err)
    }

    lastDir := workingDirectory
    myUniqueRelativePath := "database/migrations"

    for {
        currentPath := fmt.Sprintf("%s/%s", lastDir, myUniqueRelativePath)

        fi, err := os.Stat(currentPath)

        if err == nil {
            switch mode := fi.Mode(); {
            case mode.IsDir():
                return currentPath
            }
        }

        newDir := filepath.Dir(lastDir)

        // Ooops, we couldn't find the root dir. Check that your "myUniqueRelativePath" really exists

        if newDir == "/" || newDir == lastDir {
            return ""
        }

        lastDir = newDir
    }
}

Of course it's not the most beautiful solution, but it works.当然,这不是最漂亮的解决方案,但它确实有效。

I would use an Environment Variable for the location of your application.我会使用环境变量作为您应用程序的位置。 It seems to be the best way when running go tools, as test programs can be run from a temporary location.这似乎是运行 go 工具的最佳方式,因为测试程序可以从临时位置运行。

// get home dir of app, use MYAPPHOME env var if present, else executable dir.
func exeDir() string {
    dir, exists := os.LookupEnv("MYAPPHOME")
    if exists {
        return dir
    } else {
        ex, err := os.Executable()
        if err != nil {
            panic(err)
        }
        exPath := path.Dir(ex)
        return exPath
    }
}

It's a common practice in Go to place test fixtures in same package inside testdata folder. Go 中的常见做法是将测试装置放在testdata文件夹内的同一个包中

Some examples from standard library:标准库中的一些示例:

Also, there is a post from Dave Cheney, where he suggests following code:此外,还有来自 Dave Cheney 的帖子,他建议使用以下代码:

f, err := os.Open("testdata/somefixture.json")

I currently use a neat solution for this problem, instead of opening the file directly by calling os.Open() , I use the embed package in a smart way:我目前使用一个简洁的解决方案来解决这个问题,而不是通过调用os.Open()直接打开文件,我以一种聪明的方式使用嵌入 package:

First I create a global variable in my root package called:首先,我在根目录 package 中创建了一个全局变量,名为:

//go:embed config/* otherdirectories/*
var RootFS embed.FS

Then I just open the files inside my tests by using this global variable, eg:然后我使用这个全局变量打开测试中的文件,例如:

func TestOpenConfig(t *testing.T) {
    configFile, err := rootpkg.RootFS.ReadFile("config/env")
    if err != nil {
        t.Fatalf("unable to open config/env file: %s", err)
    }

    if string(configFile) != "FOO=bar\n" {
        t.Fatalf("config file contents differ from expected: %s", string(configFile))
    }
}

This is a neat trick because now you can always work with relative paths from your root package, which is what I used to do in other programming languages.这是一个巧妙的技巧,因为现在您始终可以使用根目录 package 的相对路径,这是我以前在其他编程语言中所做的。

Of course, this has the restriction that you will need to import your root package, which depending on your package layout might not be ideal because of cyclic imports.当然,这有一个限制,你需要导入你的根 package,这取决于你的 package 布局,因为循环导入可能并不理想。 If this is your case you might just create a embed.go file inside the config directory itself and call your configs by name.如果是这种情况,您可能只需在配置目录本身内创建一个embed.go文件,然后按名称调用您的配置。

One other drawback is that you are embedding test files in your binary, this is probably ok if your test files are not very big, like megabytes big, so I don't really mind this issue.另一个缺点是你在二进制文件中嵌入了测试文件,如果你的测试文件不是很大,比如几兆字节,这可能没问题,所以我不太介意这个问题。

I also created a repository for illustrating this solution:我还创建了一个存储库来说明这个解决方案:

https://github.com/VinGarcia/golang-reading-files-from-testshttps://github.com/VinGarcia/golang-reading-files-from-tests

Go 1.20 is getting new -C arguments for "go subcommands" so this should help: Go 1.20 正在为“go 子命令”获取新的-C arguments,因此这应该有所帮助:

go test -C directory/ ...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM