Excellent code habits must be accompanied by unit testing, which is also the philosophy of go language design;
Many foreign companies and excellent programmers pay more attention to TDD, but it is very rare in China; (TDD: Test Driven Development)
In any case, learning and using golang unit tests is not a waste of time, but to make your code more elegant and robust!
Test file
File name in_ test. All files with go suffix are test code, which will be captured by go test test and will not be compiled by go build;
Test function
There are three types of functions in the test file:
- Unit Test function: function name prefix Test; Logic of Test program
- Benchmark function: function name prefix benchmark; Test function performance
- Example function: function name prefix example; Will appear in godoc and provide sample documents for the documents
Test command
The test in Go language depends on the go test command; Add different parameters to achieve different test purposes; They will be introduced one by one later;
The go test command will traverse all the tests*_ test. Test functions in the go file that conform to the above naming rules;
Then a temporary main package is generated to call the corresponding test function, then build, run and report the test results, and finally clean up the temporary files generated in the test;
Next, unit test function, benchmark function and example function are introduced respectively:
Unit test function
Format of unit test function:
- func TestName(t *testing.T) {}
- Function names must start with Test, and optional suffixes must start with uppercase letters
- Each test function must import the testing package; About the methods in the testing package, you can take a look at the source code;
- The parameter t is used to report test failures and additional log information
A simple test function example: compare the output result with the expected result
Create business function
// File split / split Go: define a split package in which a split function is defined package split import "strings" func Split(s, sep string) (result []string) { i := strings.Index(s, sep) for i > -1 { result = append(result, s[:i]) s = s[i+1:] i = strings.Index(s, sep) } result = append(result, s) return }
Create test file
// File split/split_test.go: create a split_test.go's test file package split import ( "reflect" "testing" ) // Unit test function // The Test function name must start with Test and must receive a * testing T type parameter // 1. Directly call business functions // 2. Define expected results // 3. Compare actual results with expected results func TestSplit(t *testing.T) { got := Split("a:b:c", ":") // Call the program and return the program result want := []string{"a", "b", "c"} // Expected results if !reflect.DeepEqual(want, got) { // Because slice cannot be compared directly, it can be compared with the method in the reflection package t.Errorf("expected:%v, got:%v", want, got) // If the test fails, an error message is output } } // Provide a failed unit test func TestSplitFail(t *testing.T) { got := Split("abcd", "bc") want := []string{"a", "d"} if !reflect.DeepEqual(want, got) { t.Errorf("expected:%v, got:%v", want, got) } } // Benchmark function func BenchmarkSplit(b *testing.B) { } // Sample function func ExampleSplit() { }
Execute test command
Enter the split directory and directly run the go test command;
If you run go test -v, you can see more detailed output results: you know which test function failed and where the fault is
=== RUN TestSplit --- PASS: TestSplit (0.00s) === RUN TestSplitFail split_test.go:28: expected:[a d], got:[a cd] --- FAIL: TestSplitFail (0.00s) FAIL exit status 1 FAIL gotest/split 0.001s
Other go test commands
go test -run=?: Run corresponds to a regular expression. Only the test function whose function name matches will be executed by the go test command;
// For example, the above code executes the command: go test -v -run=Fail // This means that only test functions that can regularly match Fail will be run this time === RUN TestSplitFail split_test.go:28: expected:[a d], got:[a cd] --- FAIL: TestSplitFail (0.00s) FAIL exit status 1 FAIL gotest/split 0.001s
go test -short: skip the test function including testing Test function of short() function; It is generally used to skip test functions that are too time-consuming to execute; For example:
// Modify the TestSplitFail function in the above example code as follows func TestSplitFail(t *testing.T) { if testing.Short() { t.Skip("short The test case will be skipped in mode") } got := Split("abcd", "bc") // Call the program and return the program result want := []string{"a", "d"} // Expected results if !reflect.DeepEqual(want, got) { // Because slice cannot be compared directly, it can be compared with the method in the reflection package t.Errorf("expected:%v, got:%v", want, got) // If the test fails, an error message is output } } // Then execute the command 'go test -v -short' to print the following results: === RUN TestSplit --- PASS: TestSplit (0.00s) === RUN TestSplitFail split_test.go:25: short The test case will be skipped in mode --- SKIP: TestSplitFail (0.00s) PASS ok gotest/split 0.002s
go test -cover test coverage: coverage refers to the proportion of business code covered by test code;
go test -cover -coverprofile=c.out outputs the information related to coverage to the c.out file under the current folder;
Then execute go tool cover -html=c.out and use the cover tool to process the generated record information. This command will open the local browser window and generate an HTML report;
Sub test: it can clearly and accurately locate the error of multiple groups of test cases
Go1. A sub test is added in 7 +, and we can use t.Run to execute the sub test as follows:
func TestSplit(t *testing.T) { type test struct { input string sep string want []string } // Define multiple sets of test cases tests := map[string]test{ "simple": {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}}, "wrong sep": {input: "a:b:c", sep: ",", want: []string{"a:b:c"}}, "more sep": {input: "abcd", sep: "bc", want: []string{"a", "d"}}, "leading sep": {input: "Sand river has sand and river", sep: "sand", want: []string{"Heyou", "Another river"}}, } // t.Run is the subtest for name, tc := range tests { t.Run(name, func(t *testing.T) { // Use t.Run() to execute the subtest got := Split(tc.input, tc.sep) if !reflect.DeepEqual(got, tc.want) { t.Errorf("expected:%#v, got:%#v", tc.want, got) } }) } } // Execute go test -v === RUN TestSplit === RUN TestSplit/simple === RUN TestSplit/wrong_sep === RUN TestSplit/more_sep split_test.go:34: expected:[]string{"a", "d"}, got:[]string{"a", "cd"} === RUN TestSplit/leading_sep split_test.go:34: expected:[]string{"Heyou", "Another river"}, got:[]string{"", "\xb2\x99 Heyou", "\xb2\x99 Another river"} --- FAIL: TestSplit (0.00s) --- PASS: TestSplit/simple (0.00s) --- PASS: TestSplit/wrong_sep (0.00s) --- FAIL: TestSplit/more_sep (0.00s) --- FAIL: TestSplit/leading_sep (0.00s) FAIL exit status 1 FAIL gotest/split 0.002s
Reference function
Test program performance under a certain workload;
Basic format: func BenchmarkName(b *testing.B) {}
- Prefix with Benchmark, followed by capital letters
- There must be testing Parameter of type B;
- The benchmark test must be performed b.N times before it is comparable. The value of b.N is adjusted by the system according to the actual situation (see the following use example)
- About testing B's method can refer to the source code
An example of a simple benchmark function:
Create a test function (continue with the example code above)
func BenchmarkSplit(b *testing.B) { // Attention b.N for i := 0; i < b.N; i++ { Split("Sand river has sand and river", "sand") } }
Execute the test command - bench = $regular matching
// go test -bench=Split goos: windows goarch: amd64 pkg: go-test/split cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz BenchmarkSplit-4 4017925 311.9 ns/op PASS ok go-test/split 1.976s
The above output some information of the computer;
Where 4 in BenchmarkSplit-4 represents the value of GOMAXPROCS;
4017925 indicates the number of times the function is called;
311.9 ns/op average time spent calling this function;
// `go test -bench=Split -benchmem ` increases the statistics of memory allocation goos: windows goarch: amd64 pkg: go-test/split cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz BenchmarkSplit-4 3995856 300.9 ns/op 112 B/op 3 allocs/op PASS ok go-test/split 1.890s
Where 112 B/op indicates that 112 bytes are allocated for each operation memory;
3 allocs/op means that the memory is allocated three times for each operation;
Performance comparison function
- The benchmark function can only give absolute time consumption; In practice, we may want to know the relative time-consuming of different operations;
- The performance comparison function is usually a function with parameters, which is called by multiple different Benchmark functions with different values;
An example of a performance comparison function in a simple benchmark:
Create a fib directory and create a new fib Go file
package fib // Fib is a function that calculates the nth Fibonacci number func Fib(n int) int { if n < 2 { return n } return Fib(n-1) + Fib(n-2) }
Create a test function file FIB in the same directory_ test. go
package fib import ( "testing" ) 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) }
Execute test command
// go test -bench=Fib goos: windows goarch: amd64 pkg: go-test/fib cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz BenchmarkFib1-4 470478618 2.393 ns/op BenchmarkFib2-4 178773399 6.737 ns/op BenchmarkFib3-4 100000000 12.60 ns/op BenchmarkFib10-4 3025942 421.8 ns/op BenchmarkFib20-4 24792 55344 ns/op BenchmarkFib40-4 2 724560000 ns/op PASS ok go-interview/fib 11.675s
Benchmarkfib40-4 2 724560000 NS / op: the first is the corresponding comparison function; The second is the number of execution; The third is the average execution time;
It can be seen that the larger the input parameter value of fib function, the lower the efficiency of function execution;
- In addition to the above, the benchmark command has other parameters for other purposes: for example, these parameters include benchtime, resettimer, runparallel, setparallelism, CPU, setup, teardown and testmain
Sample function
Example functions can be used directly as documents. For example, in web-based godoc, example functions can be associated with corresponding functions or packages
Adhere to the back-end related technologies such as daily output go development + interview questions + algorithm + work experience
For my plans for this year, please check: flag-2022
For more blog content, please check bigshake