Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Idiomatic Golang Tests

Idiomatic Golang Tests

C1d23f296b6d845b95a7ca2c5fe9de4a?s=128

Hussani Oliveira

March 21, 2019
Tweet

More Decks by Hussani Oliveira

Other Decks in Programming

Transcript

  1. IDIOMATIC GOLANG TESTS A GUIDE TO

  2. WHY TO TEST?

  3. TO DOCUMENT

  4. TO ENSURE IT WORKS

  5. TO WRITE BETTER CODE

  6. HOW TO TEST?

  7. package simple_calculator func Sum(a int, b int) int { return

    a + b }
  8. 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 }
  9. $ go test -v ./ === RUN TestSumValid --- PASS:

    TestSumValid (0.00s) === RUN TestSumInvalid --- PASS: TestSumInvalid (0.00s) PASS ok simple_calculator 0.008s
  10. SUB TESTS

  11. 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 }) }
  12. $ 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
  13. TABLE DRIVEN

  14. 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}, }
  15. 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) } }) }
  16. $ 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
  17. 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
  18. 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 }
  19. 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) } }) } }
  20. $ 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
  21. FLAGS

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

  23. 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) } }) } }
  24. $ 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
  25. RACE CONDITIONS

  26. 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() }() } }
  27. $ 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
  28. $ 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()
  29. FIXTURES

  30. func TestPersistRand(t *testing.T) { data := filepath.Join( "test-fixtures", "persist-data.json.fixture" )

    // Do something on test using data }
  31. TEXT FIXTURES ▸ Use to complex string input ▸ Use

    to iterate on system folders ▸ Use to loads configuration , json, binary data, etc
  32. GOLDEN FILES

  33. 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) } ) }
  34. TEXT GOLDEN FILES ▸ Use to compare complex string output

    ▸ Need to be updated always your output changes
  35. SUMMARY

  36. 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 :)
  37. THANKS! twitter.com/hussanii