4

Is it possible to call a test Function from a non test go file to start the execution of tests?

e.g., I have a test function:

package API

import "testing"

func TestAPI(t *testing.T) { 
...
}

I need to call this from a non test go file.

package main

import "../API"

API.TestAPI()

Can I do that?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Arvind
  • 189
  • 3
  • 7
  • 15

2 Answers2

7

There are some great testing patterns in Go. Unfortunately, the way that you're trying to test is not going to work.

Your unit tests should be run using the command go test <pkg. Tests should never be called from within your code itself.

Generally there are two main forms of unit testing in Go - black and white box unit testing.

White-box testing: Testing unexported functions from within the package itself.

Black-box testing: Testing exported functions from outside the package, emulating how other packages will interact with it.

If I have a package, example, that I'd like to test. There is a simple exported function that sums a list of numbers that are provided. There is also an unexported utility function that's used by the Sum function.

example.go

package example

func Sum(nums ...int) int {
    sum := 0
    for _, num := range nums {
        sum = add(sum, num)
    }
    return sum
}

func add(a, b int) int {
    return a + b
}

example_test.go : black-box testing

Notice that I'm testing the example package, but the test sits in the example_test package. Adding the _test keeps the Go compiler happy and lets it know that this is a testing package for example. Here, we may only access exported variables and functions which lets us test the behaviour that external packages will experience when importing and using our example package.

package example_test

import (
    "testing"

    "example"
)

func TestSum(t *testing.T) {
    tests := []struct {
        nums []int
        sum  int
    }{
        {nums: []int{1, 2, 3}, sum: 6},
        {nums: []int{2, 3, 4}, sum: 9},
    }

    for _, test := range tests {
        s := example.Sum(test.nums...)
        if s != test.sum {
            t.FailNow()
        }
    }
}

example_internal_test.go : white-box testing

Notice that I'm testing the example package, and the test also sits in the example package. This allows the unit tests to access unexported variables and functions.

package example

import "testing"

func TestAdd(t *testing.T) {
    tests := []struct {
        a   int
        b   int
        sum int
    }{
        {a: 1, b: 2, sum: 3},
        {a: 3, b: 4, sum: 7},
    }

    for _, test := range tests {
        s := add(test.a, test.b)
        if s != test.sum {
            t.FailNow()
        }
    }
}

I hope that this you a better understanding on how Go's testing framework is set up and should be used. I haven't used any external libraries in the examples for simplicity sake, although a very popular and powerful package to help with unit testing is github.com/stretchr/testify.

Nick Corin
  • 2,214
  • 5
  • 25
  • 46
0

What you are attempting is possible, but not advised, as it depends on internal details of Go's testing library, which are not covered by the Go 1 Compatibly Guarantee, meaning you'll likely have to tweak this approach for every version of Go.

You should rarely, if ever, attempt this. I did this once, for a suite of API integration tests that I wanted to be able to run as normal tests, but also from a CLI tool, to test third-party implementations of the API. Now that I've done it, I'm not sure I'd do it again. I'd probably look for a different approach.

But if you can't be dissuaded, here's what you need to do:

  1. Your tests must not be defined in a file named *_test.go. Such files are excluded from normal compilation.

  2. You must trigger the tests to run from a file appropriately named *_test.go, for your normal test case.

  3. You must manually orchestrate the tests in your non-test case.

In detail:

  1. Put tests in non-test files. The testing package is just a standard package, so it's easy to include wherever you need it:

    foo.go

    package foo
    
    import "testing"
    
    func SomethingTest(t *testing.T) {
      // your tests
    }
    
  2. Run the tests from a *_test.go file:

    foo_test.go

    func TestSomething(t *testing.T) {
        SomethingTest(t)
    }
    
  3. And run the tests from your non-test files. Here you are in risky territory, as it requires using methods that are not protected by the Go 1 compatibility guarantee. You need to call the testing.MainStart method, with a manually-crafted list of tests.

    This also requires implementing your own version of testing.testDeps, which is private, but an interface, so easy to implement. You can see my Go 1.9 implementation here.

    tests := []testing.InternalTest{
        {
            Name: "TestSomething",
            f:     SomethingTest,
        },
    }
    m := testing.MainStart(MyTestDeps, tests, nil, nil)
    os.Exit(m.Run())
    

This is a very simplified example, not meant to be complete. But I hope it puts you on the right path.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189