Testing in Go – Benchmarking in Go – Coverage Analysis in Go



Testing in Go – Benchmarking in Go – Coverage Analysis in Go

0 0


meetup-june2014


On Github mkboudreau / meetup-june2014

go test

Presented by Michael Boudreau

1. Java Dev - IBM - 15 Years this November 2. Married, 3 Boys (3,5,7) who keep me busy 3. GO - 2 1/2 Months - In Spare Time 4. So if you're sittin there wondering why I'm up here, rest assured ---- I'm wondering the same thing

Agenda

  • Go's Built-in Facilities
  • Test Frameworks (if we have time)

  • Out of Scope

    • Test Strategy & Practices

      (i.e. tdd vs bdd or when to mock, stub, spy, dummy, or fake)

    • How to test specific Go types

      (i.e. channels, interfaces, structs, slices)

Testing in Go

What Go Provides
  • Simple CLI that just works
  • Benchmarking
  • Coverage analysis
  • Race condition detection
  • Example documentation

Testing in Go

 

$ go test

 

Testing in Go

The Basics
  • Source File:     lottery.go
  • Test File:           lottery_test.go
  • Run Test:          go test
1.

Testing in Go

Source File
// lottery.go
package lottery

func ProduceSixLotteryNumbers() []int {
   return []int{4,8,15,16,23,42}
}
1.

Testing in Go

Test File
// lottery_test.go
package lottery

import (
  "testing"
)

func TestXxx(t *testing.T) {
    //...
}
1.

Testing in Go

// lottery.go
package lottery

func ProduceSixLotteryNumbers() []int {
   return []int{4,8,15,16,23,42}
}
// lottery_test.go
package lottery

func TestProduceSixLotteryNumbers(t *testing.T) {
    lottery := ProduceSixLotteryNumbers()

    if lottery == nil {
        t.Errorf("lottery should not be nil")
    } else if len(lottery) != 6 {
        t.Errorf("expected lottery to be size 6, but was %v", len(lottery))
    }
}
1.

Testing in Go

Run Test
$ go test
$ go test -v
1. github.com/mkboudreau/meetup-june2014/code/lottery 2. go test 3. go test -v

Benchmarking in Go

 

$ go test -bench .

 

Benchmarking in Go

Test File
// lottery_test.go
package lottery

import (
  "testing"
)

func BenchmarkXxx(b *testing.B) {
    for i := 0; i < b.N; i++ {
        //
    }
}
1.

Benchmarking in Go

Test and Benchmark functions
func TestProduceSixLotteryNumbers(t *testing.T) {
    lottery := ProduceSixLotteryNumbers()

    if lottery == nil {
        t.Errorf("lottery should not be nil")
    } else if len(lottery) != 6 {
        t.Errorf("expected lottery to be size 6, but was %v", len(lottery))
    }
} 

func BenchmarkProduceSixLotteryNumbers(b *testing.B) {
    for i := 0; i < b.N; i++ {
        ProduceSixLotteryNumbers()
    }
}
1.

Benchmark Example

  • github.com/mkboudreau/meetup-june2014/code/lottery
  • go test -bench .
  • github.com/mkboudreau/meetup-june2014/code/algorithms
  • go test -bench .

Coverage Analysis in Go

Simple and Intuitive

 

$ go test -cover

 

Coverage Analysis in Go

Output Viewing Options

$ go test -coverprofile=cover.out

$ go tool cover -func=cover.out

$ go tool cover -html=cover.out

 

Coverage Example

  • github.com/mkboudreau/meetup-june2014/code/algorithms
  • go test -cover
  • go test -coverprofile=cover.out
  • go tool cover -func=cover.out
  • go tool cover -html=cover.out

Race Condition Detection in Go

 

$ go test -race

 

Race Condition Detection in Go

Context: My Task Runner

1. I wrote a little PROOF OF CONCEPT 2. just playing around 3. Basics: 4. Runs an async task 5. in channel 6. out channel 7. err channel 8. Reads off the in, does some work, writes output to the out

Race Condition Detection in Go

Context: My Task Runner

Race Condition Detection in Go

Context: My Task Runner

Pretty simple -- The task runner has an IN channel an OUT channel and an ERROR channel

Race Condition Detection in Go

Context: My Task Runner
type TaskData struct {
  In    <-chan interface{}
  Out   chan<- interface{}
  Error chan<- error
}

type TaskRunner interface {
  Run(data *TaskData)
}

func RunTask(data *TaskData, runner TaskRunner) {
  go func() {
    defer close(data.Out)
    runner.Run(data)
  }()
}

Race Condition Detection in Go

Context: My Task Runner
in := make(chan interface{})
out := make(chan interface{})
err := make(chan error)
taskData := &TaskData{
  In:    in,
  Out:   out,
  Error: err,
}

RunTask(taskData, &FilterString{Filter: "HELLO"})
go func() {
    in <- "TESTING A"
    in <- "123"
    in <- "HELLO"
    in <- "456"
    in <- "TESTING B"
    close(in)
}()

Race Condition Detection in Go

Context: My Task Runner

Pretty simple -- The task runner has an IN channel an OUT channel and an ERROR channel

Race Condition Detection in Go

Context: My Task Runner

Race Condition Detection in Go

Context: My Task Runner

Race Condition Detection in Go

Context: My Task Runner

Race Condition Detection in Go

Context: My Task Runner
func ChainTasks(data *TaskData, runners ...TaskRunner) {
  for i, runner := range runners {
    if i < len(runners)-1 {
      ch := make(chan interface{})
      RunTask(&TaskData{In: data.In, Out: ch, Error: data.Error}, runner)
      data.In = ch
    } else {
      RunTask(&TaskData{In: data.In, Out: data.Out, Error: data.Error}, runner)
    }
  }
}

Race Detection Example

  • github.com/mkboudreau/meetup-june2014/code/taskrunner
  • go test -race

Race Output

Main Sections
==================
WARNING: DATA RACE
Write by goroutine 5:
  // ...

Previous read by goroutine 7:
  // ...

Goroutine 5 (running) created at:
  // ...

Goroutine 7 (running) created at:
  // ...
==================
.........................................PASS
Found 1 data race(s)

Race Output

WRITE
Write by goroutine 5:
  runtime.closechan()
  .../code/taskrunner.func 001()
      .../code/taskrunner/taskrunner.go:22 +0xba
READ
Previous read by goroutine 7:
  runtime.chansend()
  .../code/taskrunner.adaptStringChannelToInterfaceChannel()
      .../code/taskrunner/taskrunner_string.go:47 +0xbd

Race Output

Write by goroutine 5: runtime.closechan()
  .../code/taskrunner.func 001()
      /Users/.../code/taskrunner/taskrunner.go:22 +0xba
func RunTask(data *TaskData, runner TaskRunner) {
    go func() {
        defer close(data.Out)
        runner.Run(data)
    }() // line 22
}
Previous read by goroutine 7: runtime.chansend()
  .../code/taskrunner.adaptStringChannelToInterfaceChannel()
      /Users/.../code/taskrunner/taskrunner_string.go:47 +0xbd
func adaptStringChannelToInterfaceChannel(in <-chan string, out chan<- interface{}, err chan<- error) {
    defer close(out)

    for someString := range in {
        out <- someString // line 47
    }
}

Race Detection Example Continued

  • edit taskrunner_string.go (comment defer close)
  • go test -race

Documentation Examples in Go

Examples in Go

Test File
package helloworld

import (
    "fmt"
)

func Example() {
    result := DoSomething()
    fmt.Println(result)

    // Output: Hello
}
1.

Examples in Go

Format of Function Names
  • Package Level Example

    func Example() {}
  • Example for Function

    func ExampleFuncName() {}
  • Example for Type

    func ExampleTypeName() {}
  • Example for Method on Type

    func ExampleTypeName_MethodName() {}
  • Example for Variant of Function (applies to all)

    func ExampleFuncName_variation() {}
1.

Examples in Go

godoc -http=:8888

  • github.com/mkboudreau/meetup-june2014/code/taskrunner
  • godoc -http=:8888
  • http://localhost:8888/pkg/github.com/mkboudreau/meetup-june2014/code/taskrunner/

Test Frameworks & Libraries in Go

Test Frameworks & Libraries in Go

Frameworks & Libraries
  • Ginkgo
  • Goblin
  • Gospecify
  • Gocheck
  • Goconvey
  • oglematchers
  • Gomega
  • Gocov
  • Gomock
1.

Test Frameworks & Libraries in Go

Pros & Cons
  • Expressive
  • Additional syntax and mini-language to learn
  • Devs familiar with syntax style
  • Potential complicated setup
  • Setup & Teardown per suite or per test
  • Additional dependencies
  • Usually compatible with Go's test facilities
1.

Test Frameworks & Libraries in Go

Ginkgo Test Framework & Gomega Matchers
func TestExamplePackage(t *testing.T) {
    RegisterFailHandler(Fail)
    RunSpecs(t, "Example Package Suite")
}
var _ = Describe("some description.... ", func() {
    Context("Some context....", func() {
        It("Should work with table-driven tests", func() {
            for _, testcase := range testcases {
                actual := MyFunction(testcase.input)

                Expect(actual).ToNot(BeNil())
                Expect(actual).To(Equal(testcase.expected))
            }
        })
        It("Should be nil with the text inconceivable", func() {
            badTestCase := "inconceivable"
            actual := MyFunction(badTestCase)
            Expect(actual).To(BeNil())
        })
    })
})
1.

Test Frameworks & Libraries in Go

GoConvey Test Framework & oglematchers Matchers
func TestSpec(t *testing.T) {
    Convey("Given some integer with a starting value", t, func() {
        x := 1

        Convey("When the integer is incremented", func() {
            x++

            Convey("The value should be greater by one", func() {
                So(x, ShouldEqual, 2)
            })
        })
    })
}
1.

Test Frameworks & Libraries in Go

My Opinions
  • Enhances Readability; hence, maintainability
  • Adds Value
  • Utilizes Go's Testing Facilities
1.

Thank You

0