Slide 1

Slide 1 text

IDIOMATIC GOLANG TESTS A GUIDE TO

Slide 2

Slide 2 text

WHY TO TEST?

Slide 3

Slide 3 text

TO DOCUMENT

Slide 4

Slide 4 text

TO ENSURE IT WORKS

Slide 5

Slide 5 text

TO WRITE BETTER CODE

Slide 6

Slide 6 text

HOW TO TEST?

Slide 7

Slide 7 text

package simple_calculator func Sum(a int, b int) int { return a + b }

Slide 8

Slide 8 text

func TestSumValid(t *testing.T) { got := Sum(2, 2) if got != 4 { t.Errorf("Sum() = %v, want %v", got, 4) } } func TestSumInvalid(t *testing.T) { // some test logic here }

Slide 9

Slide 9 text

$ go test -v ./ === RUN TestSumValid --- PASS: TestSumValid (0.00s) === RUN TestSumInvalid --- PASS: TestSumInvalid (0.00s) PASS ok simple_calculator 0.008s

Slide 10

Slide 10 text

SUB TESTS

Slide 11

Slide 11 text

func TestSum(t *testing.T) { t.Run("sum-valid", func(t *testing.T) { got := Sum(2, 2) if got != 4 { t.Errorf("Sum() = %v, want %v", got, 4) } }) t.Run("sum-invalid", func(t *testing.T) { // some test logic here }) }

Slide 12

Slide 12 text

$ go test -v ./ === RUN TestSum === RUN TestSum/sum-valid === RUN TestSum/sum-invalid --- PASS: TestSum (0.00s) --- PASS: TestSum/sum-valid (0.00s) --- PASS: TestSum/sum-invalid (0.00s) ok simple_calculator 0.008s

Slide 13

Slide 13 text

TABLE DRIVEN

Slide 14

Slide 14 text

type args struct { a int b int } type testCase struct { name string args args want int } tests := []testCase{ {"sum 2+2", args{1, 2}, 3}, }

Slide 15

Slide 15 text

for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := Sum(tt.args.a, tt.args.b) if got != tt.want { t.Errorf("Sum() = %v, want %v", got, tt.want) } }) }

Slide 16

Slide 16 text

$ go test -v ./ === RUN TestSum === RUN TestSum/sum_2+2 --- PASS: TestSum (0.00s) --- PASS: TestSum/sum_2+2 (0.00s) ok simple_calculator 0.008s

Slide 17

Slide 17 text

TEXT TABLE DRIVEN TESTS ▸ Easy to add new test cases ▸ Don’t repeat same logic ▸ Easy to reproduce bugs ▸ Can be used even for one test case

Slide 18

Slide 18 text

func Fibonacci(n uint) uint { if n <= 1 { return n } var n2, n1 = uint(0), uint(1) for i := uint(1); i < n; i++ { n2 = n2 + n1 n1, n2 = n2, n1 } return n1 }

Slide 19

Slide 19 text

func TestFibonacci(t *testing.T) { tests := []struct { name string number uint want uint }{ {"10", 10, 55}, {"50", 50, 12586269025}, {"9999999999", 9999999999, 2225628016866617058}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Fibonacci(tt.number); got != tt.want { t.Errorf("Fibonacci() = %v, want %v", got, tt.want) } }) } }

Slide 20

Slide 20 text

$ go test -v ./ === RUN TestFibonacci === RUN TestFibonacci/10 === RUN TestFibonacci/50 === RUN TestFibonacci/9999999999 --- PASS: TestFibonacci (7.49s) --- PASS: TestFibonacci/10 (0.00s) --- PASS: TestFibonacci/50 (0.00s) --- PASS: TestFibonacci/9999999999 (7.49s) PASS ok simple_calculator 7.502s

Slide 21

Slide 21 text

FLAGS

Slide 22

Slide 22 text

var skipSlow = flag.Bool("skipSlow", false, "Skip slow tests")

Slide 23

Slide 23 text

func TestFibonacci(t *testing.T) { tests := []struct { name string number uint want uint isSlow bool }{ {"10", 10, 55, false}, {"50", 50, 12586269025, false}, {"9999999999", 9999999999, 2225628016866617058, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if *skipSlow && tt.isSlow { t.Skipf("Skiped slow test") } if got := Fibonacci(tt.number); got != tt.want { t.Errorf("Fibonacci() = %v, want %v", got, tt.want) } }) } }

Slide 24

Slide 24 text

$ go test -v -skipSlow ./ === RUN TestFibonacci === RUN TestFibonacci/10 === RUN TestFibonacci/50 === RUN TestFibonacci/9999999999 --- PASS: TestFibonacci (0.00s) --- PASS: TestFibonacci/10 (0.00s) --- PASS: TestFibonacci/50 (0.00s) --- SKIP: TestFibonacci/9999999999 (0.00s) calculator_test.go:73: Skiped slow test PASS ok simple_calculator 0.008s

Slide 25

Slide 25 text

RACE CONDITIONS

Slide 26

Slide 26 text

type Item struct { Value int } func (item Item) PersistRand(n int) { rand.Seed(time.Now().UnixNano()) for i := 0; i < n; i++ { go func() { item.Value = rand.Int() }() } }

Slide 27

Slide 27 text

$ go test -v ./ === RUN TestItem_PersistRand === RUN TestItem_PersistRand/10 --- PASS: TestItem_PersistRand (0.00s) --- PASS: TestItem_PersistRand/10 (0.00s) PASS ok idiomatic-tests-examples/kv 0.010s

Slide 28

Slide 28 text

$ go test -v -race ./ === RUN TestItem_PersistRand === RUN TestItem_PersistRand/10 ================== WARNING: DATA RACE Write at 0x00c0000920c0 by goroutine 9: idiomatic-tests-examples/kv.Item.PersistRand.func1() /Users/hussani/projects/go-workspace/src/idiomatic-tests- examples/kv/storage.go:17 +0x46 Previous write at 0x00c0000920c0 by goroutine 8: idiomatic-tests-examples/kv.Item.PersistRand.func1()

Slide 29

Slide 29 text

FIXTURES

Slide 30

Slide 30 text

func TestPersistRand(t *testing.T) { data := filepath.Join( "test-fixtures", "persist-data.json.fixture" ) // Do something on test using data }

Slide 31

Slide 31 text

TEXT FIXTURES ▸ Use to complex string input ▸ Use to iterate on system folders ▸ Use to loads configuration , json, binary data, etc

Slide 32

Slide 32 text

GOLDEN FILES

Slide 33

Slide 33 text

func TestFetchPersist(t *testing.T) { // a table test case for _, tc := range testCases { golden := filepath.Join( "test-fixtures", tc.name + ".golden" ) expected, _ := ioutil.ReadFile(golden) if got := doSomething(tc.input); got != expected { t.Errorf("doSomething() result doesn't match golden. got %v", got) } ) }

Slide 34

Slide 34 text

TEXT GOLDEN FILES ▸ Use to compare complex string output ▸ Need to be updated always your output changes

Slide 35

Slide 35 text

SUMMARY

Slide 36

Slide 36 text

TEXT SUMMARY ▸ Always do tests! They are important ▸ Test using Table Driven ▸ Use flags to control some test execution ▸ Always execute tests with race flag ▸ Don’t put complex or long strings on your test file ▸ Use Fixtures & Golden files ▸ Happy testing :)

Slide 37

Slide 37 text

THANKS! twitter.com/hussanii