Testing Go Program in Nutshell.

byAaditya Chapagain
Go Golang Test Testing software development Programming language TDD

Testing Framework in Go is extremely simple and minimal

Table of contents

  1. The GO test Tool
  2. Test Functions
  3. Benchmark Function

The GO test Tool

Testing in Go starts with go test subcommand i.e. test driver for Go packages that are organized according to certain conventions. go test command specifically looks and execute files that ends with _test.go which are not part of the package when built by go build .

The Go test files contains three kinds of special functions :

  • A test function: Function which name starts with Test. This function exercises some program logic for correct behaviour; subcommand go test calls the test function and reports the result which is either PASS or FAIL.

  • A benchmark function: It has the name beginning with Benchmark and measures the performance of some operations; subcommand go test reports the mean execution time of the operation.

  • An example function: Its name starts with Example provides machine-checked documentation.

The go test tool scans the *_test.go files for these special functions, generates a temporary main package that calls them all in the proper way, builds and runs it, reports the result, and then cleans up.

Test Functions

Each test file must import the testing package. Test functions has following signature :

func TestName(t *testing.T) {
  // ..
}

Test function names must begin with Test; The optional suffix Name must begin with a capital letter:

func TestSin(t *testing.T) { // ... }
func TestCos(t *testing.T) { // ... }

The t parameter provides methods for reporting test failures and logging additional information. Lets create a new package palindrome containing a single function IsPalindrome that reports whether a string a string reads same forward and backward.

package palindrome

func IsPalidrome(s string) bool {
  for i := range s {
    if s[i] != s[len(s) - 1 - i] {
      return false
    }
  }
  return true
}

In the same directory, the palindrome_test.go contains two test functions named TestPalindrome and TestNonPalindrome. Each check that IsPalindrome gives the right answer for a single input and reports failures using t.Errorf:

package palindrome

func TestPalindrome(t *testing.T) {
  if !IsPalindrome("detartrated") {
    t.Errorf(`IsPalindrome("detartrated") = false`)
  }
  if !IsPalindrome("kayak") {
    t.Errorf(`IsPalindrome("kayak") = false`)
  }
}

func TestNonPalindrome(t *testing.T) {
  if IsPalindrome("palindrome") {
    t.Error(`IsPalindrome("palindrome") = true`)
  }
}

Now,we already have test code to test our functionality, we can use go test command to run tests in go. Remember go test palindrome will run the package level test.For this to work you have to place your package in either $GOROOT or $GOPATH directory. In our case we can test our functionlity by going into pacakge directory.

> cd <path_to_project_dir>/palindrome
> go test

PASS
ok  	<path_to_project_dir>/palindrome	0.001s

To look into more details on test function, To see which one failed and which one succeed, we can use -v flag , which will prints the name and execution time of each test in the package.

> go test -v

=== RUN TestPalindrome
--- PASS: TestPalindrome (0.00s)
=== RUN TestNonPalindrome
--- PASS: TestNonPalindrome (0.00s)
=== RUN TestFrenchPalindrome
--- FAIL: TestFrenchPalindrome (0.00s)
word_test.go:28: IsPalindrome("été") = false
=== RUN TestCanalPalindrome
--- FAIL: TestCanalPalindrome (0.00s)
word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false
FAIL
exit status 1
FAIL  <path_to_project_dir>/palindrome 0.001s

and the -run flag, whose argument is a regular expression, causes go test to run only those tests whose function name matches the patter:


> go test -v -run="French|Canal"
=== RUN TestFrenchPalindrome
--- FAIL: TestFrenchPalindrome (0.00s)
word_test.go:28: IsPalindrome("été") = false
=== RUN TestCanalPalindrome
--- FAIL: TestCanalPalindrome (0.00s)
word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false
FAIL
exit status 1
FAIL  <path_to_project_dir>/palindrome 0.001s

Benchmark Function

Benchmarking is the practice of measuring the performance of a program on a fixed workload. In Go, a benchmark function look like a test function, but with the Benchmark prefix and a *testing.B parameter that provides most of the same methods as a *testing.T, plus few extra related performance measurement.It also exposes an Integer field N, which specifies the number of times to perform the operation being measured.

Here's a benchmark for IsPalindrome that calls it N times in a loop.

func BenchmarkIsPalindrome(b *testing.B) {
  for i := 0; i < b.N; i++ {
    IsPalindrome("A man, a plan, a canal: Panama")
  }
}

Unlike tests, by default no benchmarks are run. The argument to the -bench flag selects which benchmark to run. It is regular expression matching the names of Benchmark functions, with a default value that matches none of the functions. The "." pattern causes it to match all benchmark functions in package.

> go test -bench=.
goos: linux
goarch: amd64
BenchmarkIsPalindrome-12    	 4377900	       301 ns/op
PASS
ok  	<path_to_project_dir>/palindrome	1.598s

The benchmark name’s numeric suffix, 12 here , indicates the value of GOMAXPROCS, which is important for concurrent benchmarks.

The report tells us that each call to IsPalindrome took about 0.301 microseconds, averaged over 4377900 runs. Since the benchmark runner initially has no idea how long the operation takes, it make some initial measuremetns using small values of N and then extrapolates to a value large enough for a stable timing measurement to be made.

2024 Aaditya Chapagain
All Rights Reserved