Testing and Benchmarking in Go with Examples

Last updated Oct 18, 2021

Software testing is an essential part in software development. Software testing is used to check if the software can be used effectively and also to check if the software is created correctly based on the specified requirements. In this Go language series tutorial we will create a test cases.

 

Create a Test

In Go, the software testing can be done inside file with _test.go notation. This is the basic syntax to create a test in Go.

func TestFuncName(t *testing.T) {
    // code for test case..
}

 

In this example, the multiply() function is tested. This function is created inside main.go file then this function is tested inside main_test.go file.

Code inside main.go.

package coolpow

// multiply returns multiply result of two numbers
func multiply(x, y int) int {
    return x * y
}

 

Code inside main_test.go.

package coolpow

import "testing"

// create a test for multiply function
func TestMultiply(t *testing.T) {
    // execute function
    var result int = multiply(3, 4)

    // give the expected result
    var expected int = 12

    // if result is not equal the expected
    if result != expected {
        // show the expected and result value
        t.Error("Expected: ", expected, " Got: ", result)
    }
}

 

Execute the test by using go test command. This is the output if the test is executed.

PASS
ok      github.com/nadirbasalamah/coolpow       3.489s

 

Based on the test code above, the test function is created. Inside this function, the multiply() function is called and stored inside result variable then the value from result and expected is checked if these two values are equal. If the values are equal then the test is passed. Otherwise, if the values are not equal then the test is failed then the value from expected and result is shown.

This is the example if the test is failed.

package coolpow

import "testing"

// create a test for multiply function
func TestMultiply(t *testing.T) {
    // execute function
    var result int = multiply(3, 4)

    // give the expected result
    var expected int = 11

    // if result is not equal the expected
    if result != expected {
        // show the expected and result value
        t.Error("Expected: ", expected, " Got: ", result)
    }
}

Output (use go test command)

--- FAIL: TestMultiply (0.00s)
    main_test.go:16: Expected:  11  Got:  12
FAIL
exit status 1
FAIL    github.com/nadirbasalamah/coolpow       3.584s

 

This is the example of testing with many test cases.

package coolpow

import "testing"

// create a struct to store test case
type testCase struct {
    dataOne  int
    dataTwo  int
    expected int
}

// create a test for multiply function
func TestMultiply(t *testing.T) {
    // create test cases
    var testCases []testCase = []testCase{
        {2, 2, 4},
        {5, 6, 30},
        {7, 7, 49},
        {5, 8, 40},
    }

    // execute test for many test cases
    for _, testCase := range testCases {
        var result int = multiply(testCase.dataOne, testCase.dataTwo)
        // if test failed, show the expected and result value
        if result != testCase.expected {
            t.Error("Expected: ", testCase.expected, " Got: ", result)
        }
    }
}

Output

PASS
ok      github.com/nadirbasalamah/coolpow       3.477s

 

Based on the code above, the dedicated struct to store test case is created. Inside the test function, many test cases is created in slice of testCase. The test for each test case is executed using for range.

 

Code Coverage Test

Code coverage test is a test to ensure if all parts of code are necessary and there is no unused code (a.k.a. junk code). The code coverage test can be used to reduce the junk code so the code that is written is effective and efficient.

In this code coverage test example, the Pow() function is created in main.go file.

Code inside main.go.

package coolpow

// create a pow function
func Pow(x, y float64) float64 {
    if y == 0 {
        return 1
    } else if y == 1 {
        return x
    } else {
        var result float64 = 1
        for i := 0; i < int(y); i++ {
            result *= x
        }
        return result
    }
}

 

The test for Pow() function is created inside main_test.go.

Code inside main_test.go.

package coolpow

import "testing"

// create a test for Pow() function
func TestPow(t *testing.T) {
    var result float64 = Pow(2, 3)

    var expected float64 = 8

    if result != expected {
        t.Error("Expected: ", expected, " Got: ", result)
    }
}

 

To execute a code coverage test use go test -cover command.

PASS
coverage: 75.0% of statements
ok      github.com/nadirbasalamah/coolpow       3.612s

 

Based on the output, the result of code coverage test is 75.0%.

There are other commands that can be used to execute code coverage test.

  • go test -coverprofile : Execute code coverage test then save the result in dedicated file.

  • go tool cover -html : Execute code coverage test then save the result in dedicated file. The result can be viewed in a web page.

This is the result from go test -coverprofile result.txt command. The test result is saved inside result.txt file.

mode: set
github.com/nadirbasalamah/coolpow/main.go:4.32,5.12 1 1
github.com/nadirbasalamah/coolpow/main.go:5.12,7.3 1 0
github.com/nadirbasalamah/coolpow/main.go:7.8,7.19 1 1
github.com/nadirbasalamah/coolpow/main.go:7.19,9.3 1 0
github.com/nadirbasalamah/coolpow/main.go:9.8,11.31 2 1
github.com/nadirbasalamah/coolpow/main.go:14.3,14.16 1 1
github.com/nadirbasalamah/coolpow/main.go:11.31,13.4 1 1

This is the result from go tool cover -html result.txt. The result is shown in a web page.

Go Test Cased Code Coverage Result

 

Create a Benchmark

Benchmarking is an activity to measure the performance of a code. Benchmarking is used to ensure the code can be executed efficiently. This is the basic syntax to create a benchmark in Go.

func BenchmarkFuncName(b *testing.B) {
    // code for benchmarking
}

 

Before the benchmark is created, the another function called OtherPow() is created inside main.go.

// create a OtherPow function
// using math package to calculate pow
func OtherPow(x, y float64) float64 {
    return math.Pow(x, y)
}

 

The OtherPow() function is tested in main_test.go.

// create a test for OtherPow() function
func TestOtherPow(t *testing.T) {
    var result float64 = OtherPow(2, 3)

    var expected float64 = 8

    if result != expected {
        t.Error("Expected: ", expected, " Got: ", result)
    }
}

Output (use go test command)

PASS
ok      github.com/nadirbasalamah/coolpow       4.048s

 

The benchmark is created for two functions including Pow() and OtherPow() in main_test.go file.

// create a benchmark for Pow() function
func BenchmarkPow(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Pow(4, 3)
    }
}

// create a benchmark for OtherPow() function
func BenchmarkOtherPow(b *testing.B) {
    for i := 0; i < b.N; i++ {
        OtherPow(4, 3)
    }
}

 

To execute a benchmark, use go test -bench . command. This is the result of the benchmark.

goos: windows
goarch: amd64
pkg: github.com/nadirbasalamah/coolpow
BenchmarkPow-4          70403528                15.7 ns/op
BenchmarkOtherPow-4     16018756                80.2 ns/op
PASS
ok      github.com/nadirbasalamah/coolpow       10.247s

 

Based on the output, there are two results from the benchmark:

  • The benchmark result for Pow() function (BenchmarkPow-4) performs an execution 70403528 times in 15.7 nano second per operation.

  • The benchmark result for OtherPow() function (BenchmarkOtherPow-4) performs an execution 16018756 times in 80.2 nano second per operation.

This is the another example of creating benchmarks.

// create a benchmark for Pow() function
// for 2^10
func BenchmarkPow10(b *testing.B) {
    for i := 0; i < b.N; i++ {
        benchmarkPow(10, b)
    }
}

// create a benchmark for OtherPow() function
// for 2^10
func BenchmarkOtherPow10(b *testing.B) {
    for i := 0; i < b.N; i++ {
        benchmarkOtherPow(10, b)
    }
}

// create a benchmark for Pow() function
// for 2^100
func BenchmarkPow100(b *testing.B) {
    for i := 0; i < b.N; i++ {
        benchmarkPow(100, b)
    }
}

// create a benchmark for OtherPow() function
// for 2^100
func BenchmarkOtherPow100(b *testing.B) {
    for i := 0; i < b.N; i++ {
        benchmarkOtherPow(100, b)
    }
}

// create a private function
// so the function is not directly invoked
// if the benchmark is executed
func benchmarkPow(num float64, b *testing.B) {
    for i := 0; i < b.N; i++ {
        Pow(2, num)
    }
}

func benchmarkOtherPow(num float64, b *testing.B) {
    for i := 0; i < b.N; i++ {
        OtherPow(2, num)
    }
}

Output (use go test -bench . command)

goos: windows
goarch: amd64
pkg: github.com/nadirbasalamah/coolpow
BenchmarkPow10-4                   10000            312663 ns/op
BenchmarkOtherPow10-4              10000           1200028 ns/op
BenchmarkPow100-4                  10000           2518267 ns/op
BenchmarkOtherPow100-4             10000            851653 ns/op
PASS
ok      github.com/nadirbasalamah/coolpow       68.471s

 

Based on the output, the Pow() function is more efficient for smaller input like 10. The OtherPow() function is more efficient for larger input like 100.

I hope this article is helpful to learn testing and benchmarking in Go Programming Language.

Article Contributed By :
https://www.rrtutors.com/site_assets/profile/assets/img/avataaars.svg

603 Views