Unit testing 如何测试golang中的主要软件包功能?

Unit testing 如何测试golang中的主要软件包功能?,unit-testing,testing,import,go,main,Unit Testing,Testing,Import,Go,Main,我想测试主包中包含的一些函数,但我的测试似乎无法访问这些函数 我的示例main.go文件如下所示: package main import ( "log" ) func main() { log.Printf(foo()) } func foo() string { return "Foo" } package main import ( "testing" ) func Foo(t testing.T) { t.Error(foo()) }

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

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

package main

import (
    "log"
)

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

func foo() string {
    return "Foo"
}
package main

import (
    "testing"
)

func Foo(t testing.T) {
    t.Error(foo())
}
我的主_test.go文件如下所示:

package main

import (
    "log"
)

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

func foo() string {
    return "Foo"
}
package main

import (
    "testing"
)

func Foo(t testing.T) {
    t.Error(foo())
}
当我运行
go test main\u test.go
时,我得到

# command-line-arguments
.\main_test.go:8: undefined: foo
FAIL    command-line-arguments [build failed]
据我所知,即使我将测试文件移到别处并尝试从main.go文件导入,我也无法导入它,因为它是
包main


构建此类测试的正确方法是什么?我应该从
main
包中删除所有内容吗?我应该使用一个简单的main函数来运行所有内容,然后在自己的包中测试这些函数,还是在测试过程中可以从主文件中调用这些函数?

单元测试仅限于此。在某些情况下,您必须实际运行该程序。然后您测试它是否与真实的输入一起工作,从真实的来源,产生到真实目的地的真实输出。真的


如果要对某个对象进行单元测试,请将其移出main()。

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

以下是我的跑步记录:

$ ls
main.go     main_test.go
$ go test *.go
ok      command-line-arguments  0.003s
注意,在我的版本中,我在命令行上同时运行main.go和main_test.go

另外,您的_测试文件不太正确,您需要将您的测试函数命名为TestXXX,并使用指向testing.T的指针

以下是修改后的verison:

package main

import (
    "testing"
)

func TestFoo(t *testing.T) {
    t.Error(foo())
}
以及修改后的输出:

$ go test *.go
--- FAIL: TestFoo (0.00s)
    main_test.go:8: Foo
FAIL
FAIL    command-line-arguments  0.003s

在两个源中将包名从main更改为foobar。 在src/foobar下移动源文件

mkdir -p src/foobar
mv main.go main_test.go src/foobar/
确保将GOPATH设置为src/foobar所在的文件夹

export GOPATH=`pwd -P`
测试一下

go test foobar

这不是对OP问题的直接回答,我大体上同意之前的回答和评论,即
main
应该主要是打包函数的调用方。话虽如此,我发现有一种方法对测试可执行文件很有用。它使用
log.Fataln
exec.Command

  • Write
    main.go
    ,使用一个延迟函数调用log.Fatalln()在返回前将消息写入stderr
  • main\u test.go
    中,使用
    exec.Command(…)
    cmd.CombinedOutput()
    运行程序,并选择参数测试某些预期结果
  • 例如:

    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\u test.go
    中,对可能导致
    somefunc
    失败的坏参数进行测试,如下所示:

    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)
        }
    }
    
    请注意,
    err
    from
    CombinedOutput()
    是log.Fatalln对
    os.exit(1)
    的引擎盖下调用的非零退出代码。这就是为什么我们需要使用
    out
    somefunc
    提取错误消息的原因

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

    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
    ,并断言退出代码 @MikeElis的回答让我半途而废,但有一个主要部分遗漏了Go自己的flag_测试。去帮我弄清楚

    免责声明 你基本上想要运行你的应用程序并测试正确性。因此,请按照您的意愿标记此测试,并将其归入该类别。但值得尝试这种类型的测试并看到其好处。尤其是如果您正在编写CLI应用程序

    想法是像往常一样运行
    go test
    ,并且

  • 使用
    go test
    生成的应用程序的测试构建,在子流程中“自身”运行单元测试(参见第行)
  • 我们还将环境变量(见第行)传递给子进程,该子进程将执行将运行
    main
    的代码部分,并使测试以
    main
    的退出代码退出:
    if os.Getenv(SubCmdFlags) != "" {
        // We're in the test binary, so test flags are set, lets reset it so
        // so that only the program is set
        // and whatever flags we want.
        args := strings.Split(os.Getenv(SubCmdFlags), " ")
        os.Args = append([]string{os.Args[0]}, args...)
    
        // Anything you print here will be passed back to the cmd.Stderr and
        // cmd.Stdout below, for example:
        fmt.Printf("os args = %v\n", os.Args)
    
        // Strange, I was expecting a need to manually call the code in
        // `init()`,but that seem to happen automatically. So yet more I have learn.
        main()
    }
    
    注:如果主功能未退出,测试将挂起/循环
  • 然后对从子进程返回的退出代码进行断言。
    // get exit code.
    got := cmd.ProcessState.ExitCode()
    if got != test.want {
        t.Errorf("got %q, want %q", got, test.want)
    }
    
    注意:在本例中,如果返回的不是预期的退出代码,测试将从子进程输出STDOUT和/或STDERR,以帮助调试

  • 请参阅此处的完整示例:

    此视频中有一段关于测试包的视频介绍(从3m30s开始)。main()函数(理想情况下,您的包main作为一个整体)不需要测试:它应该是一个或多个库的“哑”端点。测试它们。
    main.go
    中的函数“foo”以小写的“f”开头,使其成为私有函数,因此我认为您的测试不能直接调用它,因为它没有访问权限。为什么您要指定任何文件来
    go Test
    ?是的,但是我觉得这个角色的提问有点困难,我不想把这个问题和设置GOPATH或者类似的东西混淆起来。做“去测试”行吗?(以前没有在GOPATH之外尝试过)您不希望测试文件位于主包中。如果你这样做,你会得到很多你不想要的代码和标志。答案并不能真正回答问题。这只是一个复杂的等价于调用
    go test
    ,而不指定文件。但是要实现100%的覆盖率,主要功能需要进行测试,如何实现这一点?@perrohunter 100%测试覆盖率是一个幻觉。别费心了。关于不切实际的测试覆盖率策略,我最讨厌的是有一次我在文件关闭之类的内容中添加了if err,它降低了测试覆盖率,因为我们无法模拟文件关闭错误。它可能未经测试,但这比忽略错误并假装它不会发生要好100%!我不想在这里开始一场代码覆盖率之战,但我不同意100%覆盖率只是海市蜃楼的说法。务实地说,是的,有时由于满足覆盖政策而延迟交付软件的成本超过了不覆盖manua的成本