A Brief Introduction To Testing In Go

GoMTL-03

August 30, 2016

What's special about testing in Go?

  • Testing is first class part of the language
  • Testing in Go is coding in Go (no DSL)
  • Can be integrated into go doc
  • Includes benchmarking support
  • Built into standard tooling

Filesystem layout

  • Files named *_test.go are ignored by go build and go install
  • They're compiled by go test and some functions are automatically run

Types of test

  • func Test*(t *testing.T)
  • func Example*()
  • func Benchmark*(t *testing.B)

Normal Test

package sample

import "testing"

func TestSomething(t *testing.T) {
	if 1 == 2 {
		print("The laws of math are broken!")
		t.Fail()
	}
}
/* Output:
PASS
ok  	sample	0.006s
*/

Normal Test (Better)

package sample

import "testing"

func TestSomething(t *testing.T) {
	if 2 == 2 {
		t.Error("The laws of math are working, but my test is broken!")
	}
}
/*
--- FAIL: TestSomething (0.00s)
	test_test.go:7: The laws of math are working, but my test is broken!
FAIL
FAIL	sample	0.005s
*/

Normal Test (Even Better)

package sample

import "testing"

func TestSomething(t *testing.T) {
	if val := SomeFuncInPackage(); val != 67 {
		t.Errorf("SomeFuncInPackage: got %v want %v", val, 67)
	}
}
/* Output?
--- FAIL: TestSomething (0.00s)
	test_test.go:7: SomeFuncInPackage: got 66 want 67
FAIL
FAIL	sample	0.006s
*/

Table Driven Test (Bestest)

package sample

import "testing"

func TestAddTwoNumbers(t *testing.T) {
	tests := []struct {
		Param1 int
		Param2 int
		Val    int
	}{
		{1, 1, 2},
		{2, -1, 1},
		{-3, -1, 1}, // Note: this is wrong
	}

	for _, tc := range tests {
		if val := AddTwoNumbers(tc.Param1, tc.Param2); val != tc.Val {
			t.Errorf("AddTwoNumbers(%v, %v): got %v want %v", tc.Param1, tc.Param2, val, tc.Val)
		}
	}
}
/* Output: --- FAIL: TestAddTwoNumbers (0.00s)
	foo_test.go:18: AddTwoNumbers(-3, -1): got -4 want 1
FAIL
FAIL	sample	0.007s
 */

Example from standard html package

Src | Go Doc

Example Function

package sample

import "fmt"

func Greet(name string) {
	fmt.Printf("Hello, %s!\n", name)
}

Example Test/Doc (Pass)

package sample

func ExampleGreet() {
        Greet("John")
        // Output: Hello, John!
}
/* go test output:
ok  	sample	0.005s
*/

Example Test/Doc (Fail)

package sample

func ExampleGreet() {
	Greet("Jean")
        // Output: Hello, John!
}
/* go test output:
--- FAIL: ExampleGreet (0.00s)
got:
Hello, Jean!
want:
Hello, John!
FAIL
FAIL	sample	0.006s
*/

Example Test/Doc (Unordered)

package sample

import "fmt"

func ExampleGreet() {
	Greet("Jean")
	Greet("John")
        // Unordered output: Hello, John!
	// Hello, Jean!
}
/* go test output:
ok  	sample	0.006s
*/

Benchmarks

func BenchmarkHello(b *testing.B) {
    for i := 0; i < b.N; i++ {
        fmt.Sprintf("hello")
    }
}
/* go test -bench . output:
testing: warning: no tests to run
PASS
BenchmarkHello-4	20000000	        85.4 ns/op
ok  	sample	1.805s
*/

Benchmarks

func BenchmarkHello(b *testing.B) {
    for i := 0; i < b.N; i++ {
        fmt.Sprintf("hello")
	time.Sleep(100*time.Millisecond)
    }
}
/* testing: warning: no tests to run
PASS
BenchmarkHello-4	      10	 102972252 ns/op
ok  	sample	1.142s

*/

Go 1.7 Subtests/Subbenchmarks


func TestSomething(t *testing.T) {
	// You can do some setup that both Name and AnotherName
	// might need, such as connecting to a database here.

	t.Parallel() // <-- Optional, makes the subtests run
		     // in parallel
	t.Run("Name", func(t *testing.T) { /* ... */ } )
	t.Run("AnotherName", func(t *testing.T) { /* ... */ } )
	// Do some teardown
	
	// This will run Name and AnotherName in parallel,
	// and then block until they finish before running the
	// next TestSomethingElse()
}

Thanks for your time

https://driusan.github.io/Presentations/Go-Test/