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

Idiomatic Golang Tests

Idiomatic Golang Tests

Hussani Oliveira

March 21, 2019
Tweet

More Decks by Hussani Oliveira

Other Decks in Programming

Transcript

  1. IDIOMATIC GOLANG TESTS
    A GUIDE TO

    View Slide

  2. WHY TO TEST?

    View Slide

  3. TO DOCUMENT

    View Slide

  4. TO ENSURE IT WORKS

    View Slide

  5. TO WRITE BETTER CODE

    View Slide

  6. HOW TO TEST?

    View Slide

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

    View Slide

  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
    }

    View Slide

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

    View Slide

  10. SUB TESTS

    View Slide

  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
    })
    }

    View Slide

  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

    View Slide

  13. TABLE DRIVEN

    View Slide

  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},
    }

    View Slide

  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)
    }
    })
    }

    View Slide

  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

    View Slide

  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

    View Slide

  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
    }

    View Slide

  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)
    }
    })
    }
    }

    View Slide

  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

    View Slide

  21. FLAGS

    View Slide

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

    View Slide

  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)
    }
    })
    }
    }

    View Slide

  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

    View Slide

  25. RACE CONDITIONS

    View Slide

  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()
    }()
    }
    }

    View Slide

  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

    View Slide

  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()

    View Slide

  29. FIXTURES

    View Slide

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

    View Slide

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

    View Slide

  32. GOLDEN FILES

    View Slide

  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)
    }
    )
    }

    View Slide

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

    View Slide

  35. SUMMARY

    View Slide

  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 :)

    View Slide

  37. THANKS!
    twitter.com/hussanii

    View Slide