Reputation: 9185
I want to test a few functions that are included in my main package, but my tests don't appear to be able to access those functions.
My sample main.go file looks like:
package main
import (
"log"
)
func main() {
log.Printf(foo())
}
func foo() string {
return "Foo"
}
and my main_test.go file looks like:
package main
import (
"testing"
)
func Foo(t testing.T) {
t.Error(foo())
}
when I run go test main_test.go
I get
# command-line-arguments
.\main_test.go:8: undefined: foo
FAIL command-line-arguments [build failed]
As I understand, even if I moved the test file elsewhere and tried importing from the main.go file, I couldn't import it, since it's package main
.
What is the correct way of structuring such tests? Should I just remove everything from the main
package asides a simple main function to run everything and then test the functions in their own package, or is there a way for me to call those functions from the main file during testing?
Upvotes: 65
Views: 81328
Reputation: 489
Because you set only one file for the test, it will not use other go files.
Run go test
instead of go test main_test.go
.
Also change the test function signature Foo(t testing.T)
to TestFoo(t *testing.T)
.
Upvotes: 0
Reputation: 4384
main
with flags and assert the exit codes@MikeElis's answer got me half way there, but there was a major part missing which Go's own flag_test.go help me figure out.
You essentially want to run your app and test correctness. So please label this test anyway you want and file it in that category. But its worth trying this type of test out and seeing the benefits. Especially if your a writing a CLI app.
The idea is to run go test
as usual, and
go test
makes (see line 86)main
and cause the test to exit with main
's exit code:
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()
}
NOTE: If main function does not exit the test will hang/loop.// get exit code.
got := cmd.ProcessState.ExitCode()
if got != test.want {
t.Errorf("got %q, want %q", got, test.want)
}
NOTE: In this example, if anything other than the expected exit code is returned, the test outputs the STDOUT and or STDERR from the sub-process, for help with debugging.See full example here: go-gitter: Testing the CLI
Upvotes: 3
Reputation: 1280
This is not a direct answer to the OP's question and I'm in general agreement with prior answers and comments urging that main
should be mostly a caller of packaged functions. That being said, here's an approach I'm finding useful for testing executables. It makes use of log.Fataln
and exec.Command
.
main.go
with a deferred function that calls log.Fatalln() to write a message to stderr before returning. main_test.go
, use exec.Command(...)
and cmd.CombinedOutput()
to run your program with arguments chosen to test for some expected outcome.For example:
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 ...
}
In main_test.go
,a test for, say, bad arguments that should cause somefunc
to fail could look like:
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)
}
}
Note that err
from CombinedOutput()
is the non-zero exit code from log.Fatalln's under-the-hood call to os.Exit(1)
. That's why we need to use out
to extract the error message from somefunc
.
The exec
package also provides cmd.Run
and cmd.Output
. These may be more appropriate than cmd.CombinedOutput
for some tests. I also find it useful to have a TestMain(m *testing.M)
function that does setup and cleanup before and after running the tests.
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)
Upvotes: 10
Reputation: 5
Change package name from main to foobar in both sources. Move source files under src/foobar.
mkdir -p src/foobar
mv main.go main_test.go src/foobar/
Make sure to set GOPATH to the folder where src/foobar resides.
export GOPATH=`pwd -P`
Test it with
go test foobar
Upvotes: -5
Reputation: 11626
when you specify files on the command line, you have to specify all of them
Here's my run:
$ ls
main.go main_test.go
$ go test *.go
ok command-line-arguments 0.003s
note, in my version, I ran with both main.go and main_test.go on the command line
Also, your _test file is not quite right, you need your test function to be called TestXXX and take a pointer to testing.T
Here's the modified verison:
package main
import (
"testing"
)
func TestFoo(t *testing.T) {
t.Error(foo())
}
and the modified output:
$ go test *.go
--- FAIL: TestFoo (0.00s)
main_test.go:8: Foo
FAIL
FAIL command-line-arguments 0.003s
Upvotes: 39
Reputation: 54325
Unit tests only go so far. At some point you have to actually run the program. Then you test that it works with real input, from real sources, producing real output to real destinations. For real.
If you want to unit test a thing move it out of main().
Upvotes: 21