This is the 8th day of my participation in the Gwen Challenge in November. See details: The Last Gwen Challenge in 2021.

Why write unit tests

  • Reduce bugs
  • Improve code quality
  • Rest assured refactoring

1 Introduction to basic rules

Unit testing for Go is easier to implement because the Go language provides a framework for unit testing. A framework for unit testing should follow the following rules.

  • 1. The go file for unit test code must be in the_test.goAt the end, the Go language testing tool only recognizes files that meet this rule
  • 2. The function name of the unit test must beTestAt the beginning, there are functions that can be exported to the public. Note: The best function name is Test+ the name of the method function to be tested
  • 3. The signature of the test function must receive a pointerA pointer to type testing.tAs an argument, and the test function cannot return any value

2 Go Test tool

The go test command is a driver that tests code by convention and organization. In the package directory, all source files with the suffix _test.go are part of the Go test test and will not be compiled into the final executable by the Go build.

There are three types of functions in the *_test.go file, unit test functions, benchmark functions, and sample functions. The go test command iterates through all the named functions in the *_test.go file, generates a temporary main package that calls the corresponding test function, builds and runs it, reports the test results, and finally cleans up the temporary files generated during the test.

type format role
Test functions The function name is prefixed with Test Test whether some logical behavior of the program is correct
Benchmark functions The function name is prefixed with Benchmark Test the performance of the function
The sample function The function name is prefixed with Example Provide sample documentation for documentation
## 3 Test the function
### 3.1 Test function format
Each test file function must start with_test.goEnd and importtestingPackage, the basic format of the test function (signature) is as follows:
“`go
func TestName(t *testing.T){
// Test the code bodyCopy the code

}

```go
func TestAdd(t *testing.T){ ... }
func TestSum(t *testing.T){ ... }
func TestLog(t *testing.T){ ... }
Copy the code

The parameter t is used to report test failures and additional log information. Testing.t has the following methods:

func (c *T) Error(args ...interface{})
func (c *T) Errorf(format string, args ...interface{})
func (c *T) Fail(a)
func (c *T) FailNow(a)
func (c *T) Failed(a) bool
func (c *T) Fatal(args ...interface{})
func (c *T) Fatalf(format string, args ...interface{})
func (c *T) Log(args ...interface{})
func (c *T) Logf(format string, args ...interface{})
func (c *T) Name(a) string
func (t *T) Parallel(a)
func (t *T) Run(name string, f func(t *T)) bool
func (c *T) Skip(args ...interface{})
func (c *T) SkipNow(a)
func (c *T) Skipf(format string, args ...interface{})
func (c *T) Skipped(a) bool
Copy the code

3.2 Test function Example 1

package junit

import (
	"fmt"
	"testing"
)

func sum(x,y int) int  {
	return x+y
}

func TestSum(t *testing.T) {
	sum := sum(10.20)
	expected:=30
	ifsum! = expected { t.Errorf("Expected execution %d, but got %d!", expected sum)} FMT. Println (sum)} -- -- -- -- -- -- -- -- -- -- -- -- the execution result -- -- -- -- -- -- -- -- -- -- - = = = RUN TestSum30
--- PASS: TestSum (0.00s)
PASS
Copy the code

3.3 Test function Example 2

package test

import (
	"fmt"
	"testing"
)
/ / add
func Add1(a, b int) int {
	return a + b
}

func Add2(a, b int) int {
	return a + b
}

func Add3(a, b int) int {
	return a + b
}
// 1. Unit test initializes execution
func TestMain(m *testing.M) {
	fmt.Println("Initialize here...")
	m.Run()
}

// 2. Customize the order in which unit tests are executed
func TestAll(t *testing.T) {
	t.Run("TestAdd2", testAdd2)
	t.Run("TestAdd1", testAdd1)
	t.Run("TestAdd3", testAdd3)
}

func testAdd1(t *testing.T) {
	i := Add1(5.6)
	ifi ! =11 {
		t.Errorf("Failed unit test TestAdd1")}}func testAdd2(t *testing.T) {
	i := Add2(5.6)
	ifi ! =11 {
		t.Errorf("Failed unit test TestAdd2")}}// 3. Skip the unit test with t.skipnow ()
func testAdd3(t *testing.T) {
	t.SkipNow()
	i := Add3(5.6)
	ifi ! =10 {
		t.Errorf("Failed unit test TestAdd3")}}Copy the code

4 Benchmarks

  • 1.File naming rules:

    The GO file containing the unit test code must be in the_test.goAt the end, the Go language testing tool only recognizes files that meet this rule

    Unit test file name_test.goPreferably, the preceding section should be the name of the go file in which the method being tested resides.
  • 2,Function declaration rules:The signature of the test function must receive a pointertesting.BType, and the function returns no value.
  • 3,Function naming rules:

    The function name of the unit test must beBenchmarkAt the beginning, it’s a function that can be exported to the public, preferablyBenchmark+The name of the method function to test.
  • 4,Function body design rules: b.N Is provided by the benchmark framework to control the number of loops that loop through the test code to evaluate performance.b.ResetTimer()/b.StartTimer()/b.StopTimer()Used to control timers to accurately control the time spent on performance testing code.

4.1 Benchmark function format

Benchmarking is a way to test application performance under a certain workload. The basic format of the benchmark is as follows:

func BenchmarkName(b *testing.B){
    // ...
}
Copy the code

A Benchmark test is prefixed with a *testing.B parameter B. The Benchmark test must be executed for b.N times, so that the test can be compared. Testing.B has the following methods:

func (c *B) Error(args ...interface{})
func (c *B) Errorf(format string, args ...interface{})
func (c *B) Fail(a)
func (c *B) FailNow(a)
func (c *B) Failed(a) bool
func (c *B) Fatal(args ...interface{})
func (c *B) Fatalf(format string, args ...interface{})
func (c *B) Log(args ...interface{})
func (c *B) Logf(format string, args ...interface{})
func (c *B) Name(a) string
func (b *B) ReportAllocs(a)
func (b *B) ResetTimer(a)
func (b *B) Run(name string, f func(b *B)) bool
func (b *B) RunParallel(body func(*PB))
func (b *B) SetBytes(n int64)
func (b *B) SetParallelism(p int)
func (c *B) Skip(args ...interface{})
func (c *B) SkipNow(a)
func (c *B) Skipf(format string, args ...interface{})
func (c *B) Skipped(a) bool
func (b *B) StartTimer(a)
func (b *B) StopTimer(a)
Copy the code

4.2 Sample benchmark tests

The benchmark is not executed by default, so we need to add the -bench parameter, so we run the benchmark by executing the go test-bench =Sum command.

Use go test-bench =Sum -benchmem for each operation

package junit

import (
	"testing"
)

func sum(x,y int) int  {
	return x+y
}

func BenchmarkSum(b *testing.B) {
	for i := 0; i < b.N; i++ {
		 sum(i, i+2)}} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the output -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- goos: Windows goarch: amd64 PKG: projectDemo1 / junit BenchmarkSum4 -   	1000000000	         0.606 ns/op
PASS

// Here 1000000000 refers to the number of iterations, which is determined by Go itself,
// 0.606ns/op refers to 0.606 nanoseconds each time Sum is executed.
Copy the code

4.3 Performance Comparison function

Benchmarking can only get the absolute elapsed time of a given operation, but many performance problems are the relative elapsed time between two different operations, and we often need to benchmark the implementation of two different algorithms with the same input. A performance comparison function is usually a function that takes parameters and is called by different Benchmark functions passing in different values. Here’s an example:

func benchmark(b *testing.B, size int){/ *... * /}
func Benchmark10(b *testing.B){ benchmark(b, 10)}func Benchmark100(b *testing.B){ benchmark(b, 100)}func Benchmark1000(b *testing.B){ benchmark(b, 1000)}Copy the code

4.4 Performance comparison test

package junit

import "testing"

// Fib is a function that computes the NTH Fibonacci number
func Fib(n int) int {
	if n < 2 {
		return n
	}
	return Fib(n- 1) + Fib(n2 -)}// performance comparison function written by benchmarkFib
func benchmarkFib(b *testing.B, n int) {
	for i := 0; i < b.N; i++ {
		Fib(n)
	}
}

func BenchmarkFib1(b *testing.B)  { benchmarkFib(b, 1)}func BenchmarkFib2(b *testing.B)  { benchmarkFib(b, 2)}func BenchmarkFib3(b *testing.B)  { benchmarkFib(b, 3)}func BenchmarkFib10(b *testing.B) { benchmarkFib(b, 10)}func BenchmarkFib20(b *testing.B) { benchmarkFib(b, 20)}func BenchmarkFib40(b *testing.B) { benchmarkFib(b, 40)}Copy the code

Run go test-bench =. Under the current file to benchmark the result

goos: windows
goarch: amd64
pkg: projectDemo1/junit
BenchmarkFib14 -         267679506                3.99 ns/op
BenchmarkFib24 -         125047687                9.38 ns/op
BenchmarkFib34 -         52140361                20.8 ns/op
BenchmarkFib104 -         2096516               559 ns/op
BenchmarkFib204 -           14952             70338 ns/op
BenchmarkFib404 -               1        1076715400 ns/op
PASS
ok      projectDemo1/junit      10.189s
Copy the code

4.5 Reset Time

B. Resettimer processing before will not be put into the execution time, and will not be output to the report, so you can do some operations not planned as a test report before. Such as:

package junit

import (
	"testing"
	"time"
)

func sum(x,y int) int  {
	return x+y
}

func BenchmarkSum(b *testing.B) {
	time.Sleep(5 * time.Second) // Suppose you need to do some time-consuming, unrelated operations
	b.ResetTimer()              // Reset the timer
	for i := 0; i < b.N; i++ {
		sum(i,i+2)}} -- -- -- -- -- -- -- -- -- -- -- -- the output -- -- -- -- -- -- -- -- -- -- -- -- goos: Windows goarch: amd64 PKG: projectDemo1 / junit BenchmarkSum4 -   	1000000000	         0.449 ns/op
PASS
Copy the code

4.6 Parallel Testing

Executes a given benchmark in parallel. RunParallel creates multiple Goroutines and assigns b.N to them for execution, where the default number of Goroutines is GOMAXPROCS.

Users who want to increase parallelism for non-CPU-bound benchmarks can call SetParallelism before RunParallel. RunParallel is usually used with the -CPU flag

package junit

import (
	"testing"
)

func sum(x,y int) int  {
	return x+y
}

func BenchmarkSumParallel(b *testing.B) {
	// b.setParallelism (1) // Set the number of cpus used
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			sum(12.35)}}} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the output -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- goos: Windows goarch: amd64 PKG: projectDemo1/junit BenchmarkSumParallel4 -   	1000000000	         1.04 ns/op
PASS
Copy the code

5 Example Functions

5.1 Format of the example function

The third type of function treated specifically by Go Test is the Example function, which is prefixed with Example. They have neither arguments nor return values. The standard format is as follows:

func ExampleName(a) {
    // ...
}
Copy the code

5.2 Examples Examples of functions

func ExampleSum(a) {
	fmt.Println(sum(10.31))
	fmt.Println(sum(123.233))
	// Output:
	/ / [41]
	/ / [356]
}
Copy the code

5.3 Sample code usage

  • Sample functions can be used directly as documentation, such as the Web-based GODoc that associates sample functions with corresponding functions or packages.
  • The sample function, as long as it contains // Output: is also an executable test that can be run with go test.
  • Example function provides the example code that can be run directly, can be directly ingolang.orgthegodocUsed on the document serverGo PlaygroundRun the sample code. The following is a sample function effect of the strings.ToUpper function on Playground.

6 More References

Please refer to the Chinese documentation of the Go standard Library