Golang中是否应该专门导出函数以便进行单元测试?

Golang中是否应该专门导出函数以便进行单元测试? 我的包中有一个公共函数 F,它首先调用 GetX,然后调用 FilterY,最后返回过滤后的结果。

GetX 和 FilterY 都是公共的,因为我希望对它们进行全面的单元测试。但是,它们没有实际用途,我的包的任何用户都不太可能单独调用它们。只有 F 会被调用,这使得 GetX 和 FilterY 非常适合作为私有函数。

我是否应该仅仅为了单元测试而将这两个方法保持为公共?请记住,FilterY 中有一些复杂的逻辑,因此需要大量的单元测试用例。

14 回复

这是可行的,不过你不能使用 package foo_test,而必须使用与被测试包相同的包名。

更多关于Golang中是否应该专门导出函数以便进行单元测试?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我正在一台macOS电脑上运行,会是这个原因吗?

go version
go version go1.15 darwin/amd64

如果你的 *_test.go 文件与包的源代码位于同一目录中,那么它们就是包本身的一部分,并且可以访问包中定义的所有函数,无论是公共的还是私有的。请查看 go/src 中的 Go 包本身。

当包被编译成其 .a 文件以供链接时,_test.go 文件会被忽略。请查看 go 源码目录中的 regexp 包:https://golang.org/src/regexp/

这应该没有区别,但你有没有尝试过实际使用编译器来构建代码,还是仅仅依赖你的集成开发环境没有报错?

此外,以“测试模式”编译上述程序实际上是可行的,因为如果你也忽略测试文件,那么测试就不可能进行了……

clbanning:

如果包源文件与包源代码位于同一目录中,那么它们就是包本身的一部分,并且可以访问包中定义的所有函数,无论是公共的还是私有的。请查看Go语言本身的包。

你是在没有先尝试的情况下给出建议吗?这是我尝试的第一件事,但它不起作用。

我对这个说法表示怀疑。具体来说,我有一个文件 a.go 和一个文件 a_test.go,它们的第一行都是 package mypackage。如果我在 a_test.go 中定义一个非测试函数,我可以在 a.go 中调用它。

因此,我怀疑每次导入 mypackage 时,它不会导入 a_test.go 中导入的所有包。

@clbanning@NobbZ

这是一个非常奇怪的情况。我正在使用 Visual Studio Code 和 Go 1.14:

文件 a.go:

package a

func example() {
   printSomething()
}

文件 a_test.go:

package a

import "fmt"

func printSomething() {
    fmt.Println("something")
}

这运行得很好,没有编译错误。这里发生了什么?我甚至运行了一个测试,它确实打印了。

一个 *_test.go 文件,即使它与被测试的函数在同一个包中,也不会成为你最终交付产物的一部分。

favicon.ico

go command - cmd/go - Go Packages

Go 是一个用于管理 Go 源代码的工具。

编译包时,构建工具会忽略以 _test.go 结尾的文件。

*_test.go 文件与包源代码位于同一目录中,那么它们就是包本身的一部分,并且可以访问包中定义的所有函数。

您指的是将测试放在与代码相同的包中。从长远来看,这似乎是个坏主意,因为我不希望我的包的导入者在不知情的情况下导入任何仅用于测试的 sqlmock 或其他依赖项。

废话不多说……对我来说,下面的程序无法编译,因为 printer 不可用。

$ cat main.go
package main

import "./foo"

func main() {
        foo.Print()
}
$ cat foo/print.go
package foo

func Print() {
        printer()
}
$ cat foo/print_test.go
package foo

import "fmt"

func printer() {
        fmt.Println("Foo")
}
$ go run main.go
# _/tmp/tmp.zQApmyskok/foo
foo/print.go:4:2: undefined: printer

所以,随你怎么称呼它。在你证明 _test.go 文件中的函数会泄漏之前,我相信文档。如果你确实能证明存在泄漏,那么这值得提交一个错误报告,以便修复编译器或文档。

因此我怀疑,每次导入mypackage时,它并不会从a_test.go导入整个被导入的包。

写这个只是为了确认@NobbZ所说的。

任何以_test结尾的文件在构建时都会被忽略。

❯ cat a.go
package main

func main() {
  printSomething()
}
❯ cat a_test.go
package main

import "fmt"

func printSomething() {
  fmt.Println("something")
}

尝试编译… 正确地无法从main()调用printSomething,因为它是_test文件的一部分。

❯ go build .
# _/tmp/glb
./a.go:4:3: undefined: printSomething

只需将a_test.go重命名为ab.go,它就能正常编译——不会抛出任何错误。

❯ mv a_test.go ab.go ❯ go build . ❯

当你做什么时没有编译错误?

如果我复制你的文件并尝试编译它们,会失败:

$ go build .
# _/tmp/tmp.Z4s5MMdPvS
./a.go:4:4: undefined: printSomething

为了完整性和可比性:

$ go version
go version go1.14.6 linux/amd64

这种行为在 Go 1.12 到 1.15 版本中是可复现的:

$ for i in 12 13 14; do nix run nixos.go_1_$i -c go version; nix run nixos.go_1_$i -c go build .; done
go version go1.12.17 linux/amd64
# _/tmp/tmp.Z4s5MMdPvS
./a.go:4:4: undefined: printSomething
go version go1.13.12 linux/amd64
# _/tmp/tmp.Z4s5MMdPvS
./a.go:4:4: undefined: printSomething
go version go1.14.4 linux/amd64
# _/tmp/tmp.Z4s5MMdPvS
./a.go:4:4: undefined: printSomething
$ nix run nixpkgs.go_1_15 -c go version; nix run nixpkgs.go_1_15 -c go build .
go version go1.15beta1 linux/amd64
# _/tmp/tmp.Z4s5MMdPvS
./a.go:4:4: undefined: printSomething

在Go语言中,不应该仅仅为了单元测试而将函数导出为公共方法。Go的设计哲学鼓励通过清晰的包接口来维护封装性,将内部实现细节隐藏起来。更好的做法是保持GetXFilterY为非导出(私有)函数,并通过以下方式之一进行单元测试:

  1. 将测试代码放在同一个包内:在Go中,你可以将测试文件(如mypackage_test.go)放在同一个包目录下,这样测试代码可以访问非导出函数。例如:

    // mypackage.go
    package mypackage
    
    func F() []string {
        x := getX()
        return filterY(x)
    }
    
    func getX() []string {
        // 内部实现
    }
    
    func filterY(data []string) []string {
        // 复杂逻辑
    }
    
    // mypackage_test.go
    package mypackage
    
    import "testing"
    
    func TestGetX(t *testing.T) {
        result := getX()
        // 验证结果
    }
    
    func TestFilterY(t *testing.T) {
        input := []string{"a", "b"}
        result := filterY(input)
        // 验证复杂逻辑
    }
    
  2. 使用内部包(internal):如果函数需要跨包测试但不想公开暴露,可以将它们放在internal目录下,这样只有特定父目录下的包可以访问。

  3. 通过公共函数F间接测试:如果GetXFilterY的逻辑可以通过F的输入输出覆盖,可以只测试F。但对于复杂逻辑,直接测试更可靠。

示例中,保持函数私有并直接在包内测试,既能全面覆盖单元测试,又不会污染公共API。这样可以避免用户误用内部函数,同时确保代码的可维护性。如果未来这些函数需要公开,可以随时导出,但反之则可能破坏兼容性。

回到顶部