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
.
- Write
main.go
with a deferred function that calls log.Fatalln() to write a message to stderr before returning.
- In
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)
main.go
begins with a lowercase "f" making it private, so I don't think your test can call it directly, as it would not have access. – Thoughtful