Golang:测试和工作目录

huangapple go评论81阅读模式
英文:

Golang: tests and working directory

问题

我正在为我的Go应用程序编写一些单元测试。然而,测试失败,因为它找不到配置文件。通常,二进制文件会在工作目录下的conf/*.conf路径下查找配置文件。

我尝试浏览到包含conf/的目录,并在其中运行go test,但它仍然报告文件系统找不到指定的路径。

我该如何告诉go test使用特定的目录作为工作目录,以便实际执行测试?

英文:

I'm writing some unit tests for my application in Go. The tests fail however because it cannot find the configuration files. Normally the binary looks for the configuration files in the working directory under the path 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.

How can I tell go test to use a certain directory as the working directory so that the tests may actually be executed?

答案1

得分: 56

你可以使用Caller函数来获取当前测试源文件的路径,像这样:

package sample

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

func TestGetFilename(t *testing.T) {
	_, filename, _, _ := runtime.Caller(0)
	t.Logf("当前测试文件名:%s", filename)
}
英文:

You may be able to use the Caller to get the path to the current test source file, like this:

package sample

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

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

答案2

得分: 30

我不相信这是可能的。我没有找到明确说明这一点的文档,但我相信go test始终使用包目录(包含Go源文件的目录)作为工作目录。

英文:

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.

答案3

得分: 20

作为一种解决方法,我编译了测试并从当前目录执行测试。

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

或者,如果你想要一个通用的命令,你可以使用-o选项重命名测试文件。

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

英文:

As a workaround, I compiled the test and execute the test from the current directory.

go test -c &amp;&amp; ./&lt;mypackage&gt;.test

Or, if you want a generic command that you can use, you can rename the test file with -o option.

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

答案4

得分: 15

无论工作目录在哪里,它都必须在你的项目目录下。所以我的解决方案是:

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))

你的路径应该始终从你的项目目录开始。每次在一个包中读取文件并由main.go或另一个包的单元测试访问时,它都会正常工作。

英文:

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, &quot;&lt;yourProjectDirName&gt;&quot;) {
	wd = filepath.Dir(wd)
}

raw, err := ioutil.ReadFile(fmt.Sprintf(&quot;%s/src/conf/conf.dev.json&quot;, 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. It will always work.

答案5

得分: 12

虽然不太方便,但你可以将其作为命令行变量传递,例如:

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)
}

然后像这样运行它:

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

While not really convenient, you can always pass it as a command line variable, for example :

package blah_test

import (
	&quot;flag&quot;
	&quot;fmt&quot;
	&quot;os&quot;
	&quot;testing&quot;
)

var (
	cwd_arg = flag.String(&quot;cwd&quot;, &quot;&quot;, &quot;set cwd&quot;)
)

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

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

Then run it like :

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

答案6

得分: 11

将init函数添加到你的测试包下的*_test.go文件中。
测试包将在测试函数开始之前运行此函数。

func init() {
    _, filename, _, _ := runtime.Caller(0)
    // 根据你的文件夹结构,".." 可能会有所变化
    dir := path.Join(path.Dir(filename), "..")
    err := os.Chdir(dir)
    if err != nil {
        panic(err)
    }  
}
英文:

To add init function into *_test.go under your test package.
Test package will run this function before test function start.

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

答案7

得分: 10

你可以使用os包

你可以像这样做:

func TestMyFunction(t *testing.T) {
    os.Chdir("./path")
    
    // 测试函数
    
    os.Chdir("..")
}

在os包中有几种可能性。

英文:

You can use the os package.

You would want to do something like this

    func TestMyFunction(t *testing.T) {
        os.Chdir(&quot;./path&quot;)
        
        //TEST FUNCTION
        
        os.Chdir(&quot;..&quot;)
    }

There are several possibilities in the os package.

答案8

得分: 2

我知道这是一个旧问题,但我遇到了同样的问题,尝试在我的测试中使用迁移来处理数据库,也许这个解决方案能帮助到其他人。

由于没有原生的方法来获取项目目录,你可以识别一些你知道只存在于项目根目录中的文件或目录(在我的情况下,是相对目录database/migrations)。一旦你有了这个唯一的相对目录,你可以编写一个类似下面的函数来获取项目根目录。它只是获取当前工作目录(假设它在项目的目录中)并向上导航,直到找到一个具有你知道在项目根目录中的相对目录的目录为止:

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)

        // 噢,我们找不到根目录了。检查一下你的"myUniqueRelativePath"是否真的存在

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

        lastDir = newDir
    }
}

当然,这不是最美观的解决方案,但它能够工作。

英文:

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). 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 := &quot;database/migrations&quot;

	for {
		currentPath := fmt.Sprintf(&quot;%s/%s&quot;, 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&#39;t find the root dir. Check that your &quot;myUniqueRelativePath&quot; really exists

		if newDir == &quot;/&quot; || newDir == lastDir {
			return &quot;&quot;
		}

		lastDir = newDir
	}
}

Of course it's not the most beautiful solution, but it works.

答案9

得分: 2

Go 1.20正在为"go子命令"引入新的"-C"参数,这应该会有所帮助:

go test -C 目录/ ...
英文:

Go 1.20 is getting new -C arguments for "go subcommands" so this should help:

go test -C directory/ ...

答案10

得分: 1

我遇到了类似的问题,并在这个博客上找到了解决方案:

基本上,你可以使用类似的函数来更改测试运行的文件夹:

package main

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

func MakeFunctionRunOnRootFolder() {
	_, filename, _, _ := runtime.Caller(0)
	// 根据你的文件夹结构,".." 可能会有所变化
	dir := path.Join(path.Dir(filename), "..")
	err := os.Chdir(dir)
	if err != nil {
		panic(err)
	}
}

博客链接

英文:

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 (
	&quot;os&quot;
	&quot;path&quot;
	&quot;runtime&quot;
)

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

答案11

得分: 1

我目前对这个问题有一个不错的解决方案,而不是直接调用os.Open()来打开文件,我巧妙地使用了embed包:

首先,在我的根包中创建一个全局变量:

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

然后,我只需使用这个全局变量来打开测试中的文件,例如:

func TestOpenConfig(t *testing.T) {
	configFile, err := rootpkg.RootFS.ReadFile("config/env")
	if err != nil {
		t.Fatalf("无法打开config/env文件:%s", err)
	}

	if string(configFile) != "FOO=bar\n" {
		t.Fatalf("配置文件内容与预期不符:%s", string(configFile))
	}
}

这是一个巧妙的技巧,因为现在你可以始终使用相对路径来访问你的根包,这是我在其他编程语言中常用的方法。

当然,这有一个限制,你需要导入你的根包,这可能不是理想的,因为可能会导致循环导入。如果是这种情况,你可以在配置目录本身创建一个embed.go文件,并按名称调用你的配置文件。

另一个缺点是你将测试文件嵌入到二进制文件中,如果你的测试文件不是非常大(比如几兆字节),那么这个问题可能还好,所以我并不在意这个问题。

我还创建了一个示例仓库来说明这个解决方案:

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

英文:

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:

First I create a global variable in my root package called:

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

Then I just open the files inside my tests by using this global variable, e.g.:

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

	if string(configFile) != &quot;FOO=bar\n&quot; {
		t.Fatalf(&quot;config file contents differ from expected: %s&quot;, 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.

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. If this is your case you might just create a embed.go file inside the config directory itself and call
your configs by name.

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-tests

答案12

得分: 0

在Go语言中,将测试固件放置在testdata文件夹中的同一包中是一种常见的做法。

以下是一些标准库中的示例:

此外,Dave Cheney在一篇文章中提到了以下代码建议:

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

It's a common practice in Go to place test fixtures in same package inside testdata folder.

Some examples from standard library:

Also, there is a post from Dave Cheney, where he suggests following code:

f, err := os.Open(&quot;testdata/somefixture.json&quot;)

答案13

得分: -1

我会为您的应用程序的位置使用环境变量。当运行go工具时,这似乎是最好的方法,因为测试程序可以从临时位置运行。

// 获取应用程序的主目录,如果存在MYAPPHOME环境变量,则使用该变量,否则使用可执行文件目录。
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
}
}

英文:

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.

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

huangapple
  • 本文由 发表于 2014年5月24日 23:58:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/23847003.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定