Learn to Code via Tutorials on Repl.it!

← Back to all posts
Testing in Go
ShaikhulIslam (2)

Go testing support is awesome. It has a subcommand go test that looks for *_test.go files in package folder and run all test functions that starts with Test* prefix and report success or error.

All test functions should follow this naming convention

func TestXXXX(t *testing.T) {

}

t param is used to report success/failure messages. Some useful methods are

t.Errorf    // to report an error
t.Logf      // to log something
t.Skip      // skip a test

More on testing package are here

Say, we have a package hello that defines a simple Hello function as follows that we want to add test

// file hello.go
package hello

func Hello() string {
  return "Hello World"
}

We need to create hello_test.go file in same directory

// file hello_test.go
package hello

// import testing package
import "testing"

func TestHello(t *testing.T) {
  want := "Hello World"
  if got := Hello(); got != want {
    // report mismatch
    t.Errorf("Hello() = %q, want %q", got, want)
  }
}

Now we have tests for our hello package. To test we need go to the folder hello/

$ ls
hello.go  hello_test.go

If we do go test it will run the test runner and execute all Test* functions and report success/error

$ go test
PASS
ok      main/hello  0.011s

To see which tests were run we can use verbose option go test -v

$ go test -v
=== RUN   TestHello
--- PASS: TestHello (0.00s)
PASS
ok      main/hello  0.029s

We can also specify a substring of specific test function go test -v -run="Hello" and test runner will look for test functions that uses the substring

$ go test -v -run="Hello"
=== RUN   TestHello
--- PASS: TestHello (0.00s)
PASS
ok      main/hello  0.027s

Table driven Testing

Now we added a Greet function that takes a param and return a greet message

func Greet(name string) string {
  return fmt.Sprintf("Hello %s", name)
}

Now we want to test Greet function but with different test cases, we can create a slice of struct with input, output definition and check results in a loop. This is called table driven testing.

func TestGreet(t *testing.T) {
  testCases := []struct {
    testDesc string
    input string
    output string
  } {
    {"Empty string", "", "Hello "},
    {"ASCII string", "World", "Hello World"},
    {"Unicode string", "世界", "Hello 世界"},
  }
  for _, testCase := range testCases {
    t.Run(testCase.testDesc, func(t *testing.T) {
      if got := Greet(testCase.input); got != testCase.output {
        t.Errorf("Hello() = %q, want %q", got, testCase.output)
      }
    })
  }
}

Here we uses an anonymous struct that has testDest, input and output fields, and then we create a slice of struct with our test cases.
t.Run takes an anynymous function which is similar to our TestHello function.

Go does not provide any built assertion library but there are plenty out there to import. one is testify/assert

Once we import it from github.com/stretchr/testify/assert we can use differnt assertion functions like these

assert.Equal(t, expected, actual)
assert.NotEqual(t, expected, actual)
assert.True(t, boolStmt)
assert.False(t, boolStmt)

Now we can run tests against this test as go test -v -run="Greet"

$ go test -v -run="Greet"
=== RUN   TestGreet
=== RUN   TestGreet/Empty_string
=== RUN   TestGreet/ASCII_string
=== RUN   TestGreet/Unicode_string
--- PASS: TestGreet (0.00s)
    --- PASS: TestGreet/Empty_string (0.00s)
    --- PASS: TestGreet/ASCII_string (0.00s)
    --- PASS: TestGreet/Unicode_string (0.00s)
PASS
ok      main/hello  0.009s