Testing and profiling golang program
Testing and profiling golang program
A very simple application, we will use for demo.
package main
import (
"bytes"
"fmt"
"strings"
"sync"
)
func main() {
sentence := "The quick brown fox jumps over the lazy dog"
words := Map(sentence)
rwords := process(words)
fmt.Println(reduce(rwords))
}
func process(words []string) []string {
nosOfWords := len(words)
buffChannel := make(chan string, nosOfWords)
task := new(sync.WaitGroup)
task.Add(nosOfWords)
for _, word := range words {
go func(word string) {
defer task.Done()
buffChannel <- reverse(word)
}(word)
}
task.Wait()
close(buffChannel)
rwords := make([]string, 0)
for rword := range buffChannel {
rwords = append(rwords, rword)
}
return rwords
}
func Map(sentence string) []string {
return strings.Split(sentence, " ")
}
func reduce(reverseWords []string) string {
return strings.Join(reverseWords, " ")
}
func reverse(word string) string {
var buff bytes.Buffer
for index := len(word) - 1; index >= 0; index-- {
buff.WriteString(string(word[index]))
}
return buff.String()
}
Run It!
go run main.go
or
go build
Test
Create a file and save it as main_test.go
Execute test case as
go test
Its time to add first test case, before we move ahead let me tell there are three types of test that we can write in golang
- Unit Test
- Benchmark Test
- Example Test
Unit Test
Lets write first test case
func TestMap(t *testing.T) {
//Arrange
phrase := "The quick brown fox jumps over the lazy dog"
//Act
slicedWord := Map(phrase)
//Assert
if len(slicedWord) != 9 {
t.Log("Test failed, nos of words returned are incorrect")
t.Fail()
}
}
A short representaion dipicting effect of log and Fail/Failnow combinations
Log() | Fail()/FailNow() | Net Effect | Desc |
---|---|---|---|
t.Log | t.Fail() | t.Error() | signals failure |
t.Log | t.FailNow() | t.Fatal() | abort further execution of test cases |
func TestMapForBlankSentence(t *testing.T) {
words := Map("")
if len(words) != 0 {
t.Error("TestMapForBlankSentence failed")
}
}
func TestReverse(t *testing.T) {
if reverse("Hello") != "olleH" {
t.Fatal("Reverse is incorrect")
}
}
Skipping long test
func TestReverse(t *testing.T) {
if testing.short(){
t.Skip()
}
if reverse("Hello") != "olleH" {
t.Fatal("Reverse is incorrect")
}
}
func TestReverse(t *testing.T) {
if testing.Verbose(){
b.Skip("Reverse skipped")
}
if reverse("Hello") != "olleH" {
t.Fatal("Reverse is incorrect")
}
}
Executing tests in parallel
Add t.Parallel() in test case, to flag test runner that execute this test in parallel.
func TestReverse(t *testing.T) {
t.Parallel()
if reverse("Hello") != "olleH" {
t.Fatal("Reverse is incorrect")
}
}
Table Driven Test
Create a slice of anonymous type and use iteratively to check for series of inputs.
func TestReverseForMultipleInput(t *testing.T){
if testing.Short() {
t.Skip("TestReverseForMultipleInput skipped")
}
tests := []struct{
input string
output string
}{
{"Hello", "olleH"},
{"benchmarks", "skramhcneb"},
{"provide","edivorp"},
{"flag","galf"},
}
for _,test := range tests {
if reverse(test.input) != test.output {
t.Fatal("TestReverseForMultipleInput failed")
}
}
}
Code Coverage
go test -cover
or generate a coverage profile and view it in web.
go test -coverprofile=cover.out
go tool cover -html=cover.out
Benchmark
A stand alone benchmark incorporated in a main program, but it is always nice to have benchmark in a test suite
br := testing.Benchmark(func( b *testing.B){
/*
is a stand alone function that runs independtly of test runner.
*/
})
// here we can get the benchmark metrics
Lets write a first bechmark to see the metrics.
func BenchmarkMap(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
Map("There is no flag you can provide, that will run only benchmarks")
}
}
Add b.ReportAllocs()
in code as above for getting allocation metrics, while performing the benchmarking alternatively we get this from a
benchmem
flag. Also, there is no flag you can provide, that will run only benchmarks (or only one benchmark).
The only flags related to these are:
bench regexp
Run benchmarks matching the regular expression. By default, no benchmarks run.run regexp
Run only those tests and examples matching the regular expression.
using these flags in association with other flags will fire on specfified benchmark
go test -bench=Map$ -run=^$ -benchmem
BenchmarkMap-4 100000 15030 ns/op 41056 B/op 37 allocs/op
PASS
ok github.com/VimleshS/go_technext 5.062s
Timers
in benchmark. Moment we kick off benchmark test, becnhmark start time is logged, but there are certain time that we need to reset/pause monitoring time. e.q while arranging for benchmark test. There are properties provided by testing package to do this set of operation.
- b.ResetTimer()
- b.StopTimer()
- b.StartTimer()
func BenchmarkProcess(b *testing.B) {
b.StopTimer()
words := []string{"There", "is", "no", "flag", "you", "can", "provide", ",",
"that", "will", "run", "only", "benchmarks"}
b.StartTimer()
for i := 0; i < b.N; i++ {
process(words)
}
}
Generating profiles
CPU profiling
go test -bench=Map$ -run=^$ -cpuprofile=cpu.prof
go tool pprof Word_reversal.test.exe cpu.prof
Memory profile
Flag | Desc |
---|---|
inuse_space | Display in-use memory size |
inuse_objects | Display in-use object counts |
alloc_space | Display allocated memory size |
alloc_objects | Display allocated object counts |
go test -bench=Reverse$ -run=^$ -memprofile=prof.mem
go tool pprof --alloc_space Word_reversal.test.exe prof.mem
Block profile
go test -run=^$ -bench=Process$ -blockprofile=bloc.prof
go tool pprof Word_reversal.test.exe bloc.prof
Contention profile
func BenchmarkReduce(b *testing.B){
words := []string{"olleH", "skramhcneb" }
b.SetParallelism(30)
b.RunParallel(func (pb *testing.PB){
for pb.Next(){
reduce(words)
}
})
}