我想测试主包中包含的一些函数,但我的测试似乎无法访问这些函数。

我的main.go示例文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
   "log"
)

func main() {
    log.Printf(foo())
}

func foo() string {
    return"Foo"
}

我的主 est.go文件如下:

1
2
3
4
5
6
7
8
9
package main

import (
   "testing"
)

func Foo(t testing.T) {
    t.Error(foo())
}

当我运行go test main_test.go时,我得到

1
2
3
# command-line-arguments
.\main_test.go:8: undefined: foo
FAIL    command-line-arguments [build failed]

据我所知,即使我将测试文件移到别处并尝试从main.go文件导入,我也无法导入它,因为它是package main

构建此类测试的正确方法是什么?我应该将main包中的所有内容都作为一个简单的主函数来运行所有内容,然后在它们自己的包中测试函数,还是有一种方法可以在测试期间从主文件调用这些函数?

  • 在这段视频中有一个关于测试包的很好的视频介绍(从3M30开始)https://youtu.be/xcsl89ytqcs?T=3M30S
  • 您的main()函数(理想情况下,包main作为一个整体)不需要测试:它应该是一个或多个库的"哑"端点。测试那些。

在命令行上指定文件时,必须指定所有文件

这是我的跑步记录:

1
2
3
4
$ ls
main.go     main_test.go
$ go test *.go
ok      command-line-arguments  0.003s

注意,在我的版本中,我在命令行上同时运行了main.go和main_test.go。

另外,您的测试文件并不完全正确,您需要将您的测试函数称为testxxx,并获取一个指向testing.t的指针。

以下是修改后的版本:

1
2
3
4
5
6
7
8
9
package main

import (
   "testing"
)

func TestFoo(t *testing.T) {
    t.Error(foo())
}

修改后的输出:

1
2
3
4
5
$ go test *.go
--- FAIL: TestFoo (0.00s)
    main_test.go:8: Foo
FAIL
FAIL    command-line-arguments  0.003s
  • 为什么要向go test指定任何文件?不要。
  • 是的,但是我觉得这个角色问的有点困难,我不想把问题和设置GoPath或者其他类似的事情混淆。做"去测试"行吗?(以前没有在Gopath外面尝试过)
  • 您不希望测试文件位于主包中。如果你这样做了,你会得到很多你不想要的代码和标志。

单元测试只进行到目前为止。在某个时刻,你必须实际运行程序。然后测试它是否与真实的输入一起工作,从真实的来源,产生真实的输出到真实的目的地。真的。

如果你想对一个东西进行单元测试,把它移出main()。

  • 但是要达到100%的覆盖率,主要功能需要测试,如何才能实现呢?
  • @Perrohunter 100%测试覆盖率是一个幻影。别费事了。我最讨厌不切实际的测试覆盖率策略的是,有一次我向类似文件关闭这样的内容添加了if-err,它减少了测试覆盖率,因为我们无法模拟文件关闭错误。它可能没有经过测试,但这比忽略错误并假装它不会发生要好100%。
  • 我不想在这里发动一场代码覆盖率的狂暴战争,但我不同意100%覆盖率是一个幻影的说法。实际上,是的,有时由于满足覆盖率策略而延迟软件交付的成本超过了不覆盖手动测试代码的成本。但是当你说你不能模拟一个文件关闭错误时,那实际上是一个设计缺陷。这就是接口在Go中非常有用的地方。例如,"func foo(file os.file)"不像"func foo(file io.readwritecloser)"那么容易测试。

这不是对OP问题的直接回答,我一般同意事先的回答和评论,敦促main主要是打包函数的调用者。也就是说,这里有一种方法可以用来测试可执行文件。它利用了log.Fatalnexec.Command

百万千克1使用一个延迟函数写入main.go,该函数在返回之前调用log.fatalln()将消息写入stderr。百万千克1百万千克1在main_test.go中,使用exec.Command(...)cmd.CombinedOutput()运行程序,并选择参数来测试某些预期结果。百万千克1

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
    // Ensure we exit with an error code and log message
    // when needed after deferred cleanups have run.
    // Credit: https://medium.com/@matryer/golang-advent-calendar-day-three-fatally-exiting-a-command-line-tool-with-grace-874befeb64a4
    var err error
    defer func() {
        if err != nil {
            log.Fatalln(err)
        }
    }()

    // Initialize and do stuff

    // check for errors in the usual way
    err = somefunc()
    if err != nil {
        err = fmt.Errorf("somefunc failed : %v", err)
        return
    }

    // do more stuff ...

 }

main_test.go中,对可能导致somefunc失败的坏论点的测试可能看起来如下:

1
2
3
4
5
6
7
8
9
10
func TestBadArgs(t *testing.T) {
    var err error
    cmd := exec.Command(yourprogname,"some","bad","args")
    out, err := cmd.CombinedOutput()
    sout := string(out) // because out is []byte
    if err != nil && !strings.Contains(sout,"somefunc failed") {
        fmt.Println(sout) // so we can see the full output
        t.Errorf("%v", err)
    }
}

注意,来自CombinedOutput()err是日志的非零退出代码。fatalln在对os.Exit(1)的hood调用下。这就是为什么我们需要使用outsomefunc中提取错误消息的原因。

exec包还提供cmd.Runcmd.Output。对于某些测试,这些可能比cmd.CombinedOutput更合适。我还发现有一个TestMain(m *testing.M)函数可以在运行测试之前和之后进行设置和清理。

1
2
3
4
5
6
7
8
9
10
func TestMain(m *testing.M) {
    // call flag.Parse() here if TestMain uses flags
    os.Mkdir("test", 0777) // set up a temporary dir for generate files

    // Create whatever testfiles are needed in test/

    // Run all tests and clean up
    exitcode := m.Run()
    os.RemoveAll("test") // remove the directory and its contents.
    os.Exit(exitcode)

在两个源中将包名称从MAIN更改为FOOBAR。将源文件移到src/foobar下。

1
2
mkdir -p src/foobar
mv main.go main_test.go src/foobar/

确保将gopath设置为src/foobar所在的文件夹。

1
export GOPATH=`pwd -P`

用测试它

1
go test foobar

  • 答案并不能真正回答问题。这就相当于在不指定文件的情况下调用go test