gogo-testing

Unable to test internal functions in Golang


I'm using Go 1.24.3, installed via Homebrew on an Apple M1, running Sequoia 15.5.

I have a main:

//main.go
package main
import "fmt"
func main() {
    fmt.Println("Hello, world!")
}

All OK so far (obviously), compiles and runs.

I wanted to test an Example and added:

//main_test.go
package main
func ExampleMain() {
    main()
    //Output: Hello, world!
}

This results in

$ go test
# blah/hello
# [blah/hello]
./main_test.go:3:1: ExampleMain refers to unknown identifier: Main
FAIL    blah/hello [build failed]

But changing the main.go as follows works (Note the example is NOT changed, still calls the main() function,with lower case 'main')

//main.go
package main
import "fmt"
func main() {
    Main()
}

func Main() {
    fmt.Println("Hello, world!")
}

Running go test now results in:

go test
PASS
ok      blah/hello  0.005s

My understanding is that *_test files should have access to the internal (lower case) functions of other functions in the same package.

I also understand there is different handling for the main package, but I cannot see any documentation that the tests should work differently for main.

Personally, I think this is that the 'ExampleFunction' needs a 'Function' function, regardless before it will compile/run. I'm assuming test (non example) funcitons are named and work the same way ('TestFunction').

In this case how should I name/define tests against internal functions?


Here's the go env:

$ go env -json GOROOT GOPATH GOBIN GOVERSION GOARCH GOARM64
{
    "GOARCH": "arm64",
    "GOARM64": "v8.0",
    "GOBIN": "",
    "GOPATH": "/Users/graham.kelly/prj/golang",
    "GOROOT": "/opt/homebrew/Cellar/go/1.24.3/libexec",
    "GOVERSION": "go1.24.3"
}

Edit (and comments) based on request from yshavit

Here's the go.mod contents, the current working directory and it's contents.

$ cat go.mod
module blah/hello

go 1.24.3

$ pwd
/Users/graham.kelly/prj/golang/hello

$ ls -l
total 24
-rw-r--r--  1 graham.kelly  staff   37 28 May 12:28 go.mod
-rw-r--r--@ 1 graham.kelly  staff   71 28 May 17:22 main_internal_test.go
-rw-r--r--@ 1 graham.kelly  staff  156 28 May 17:37 main.go

AFAIK, the files are top level wrt. the go.mod, unless there's some nuance I have not extracted from the docs yet.

My sequence was

  1. create the project folder and cd into it
  2. Run go mod init blah/hello
  3. create the main.go and run it locally
  4. create the test/example file and attempt to run it

Note obviously this was not the exact sequence. There was fiddling with git and my editor, but effectively it was the above.


Solution

  • The Examples documentation says:

    The naming convention to declare examples for the package, a function F, a type T and method M on type T are:

    func Example() { ... } 
    func ExampleF() { ... } 
    func ExampleT() { ... } 
    func ExampleT_M() { ... }
    

    Multiple example functions for a package/type/function/method may be provided by appending a distinct suffix to the name. The suffix must start with a lower-case letter.

    func Example_suffix() { ... }
    func ExampleF_suffix() { ... }
    func ExampleT_suffix() { ... }
    func ExampleT_M_suffix() { ... }
    

    The name ExampleMain matches the pattern ExampleF and ExampleT where F is the name of an exported function and T is the name of an exported type.

    The go test command runs the go vet command. The go vet command reports an error because the package does not have an exported function or type named Main.

    Fix by associating the example with the package, an exported type, an exported function or an exported method on an exported type

    In the following code, the example is associated with the package and has the suffix "jasmine". The suffix "jasmine" can be replaced with any unexported identifier. I chose my name because I am awesome.

    package main
    
    func Example_jasmine() {
        main()
        //Output: Hello, world!
    }
    

    Run it on the Playground!