In depth understanding of Golang's unit testing and performance testing
- 2020-06-07 04:40:35
- OfStack
preface
Everybody do development should all know, 1 point is very important in the development process is to test, how can we guarantee the quality of the code, how to ensure that each function is run, run results are correct, and how to ensure that written code performance is good, we know that the unit test is focused on discovery program design or implementation logic errors, making the problem as soon as possible, facilitate the positioning of the solution, but the performance test is focused on finding 1 some problems in the design of the program, make online program can under the condition of the high concurrency can remain stable. This section introduces you to unit testing and performance testing in Go with a list of these questions.
Come with a lightweight go language test framework testing and own go test commands to implement unit tests and performance tests, testing framework and is similar to the test framework in other languages, you could write for corresponding function based on the framework of test cases, and can also be based on the framework to write the corresponding pressure test cases, then let us see how to write under 1 November.
How do I write test cases
Due to the
go test
The command can only execute all files in one corresponding directory, so we next create a new project directory, gotest, where all of our code and test code resides.
Next we create two files under this directory: gotest.go and gotest_test.go
1. gotest. go: In this file, we created a package, in which there is a function to achieve the division operation:
package gotest
import (
"errors"
)
func Division(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New(" The divisor cannot be zero 0")
}
return a / b, nil
}
gotest_test.go: This is our unit test file, but keep these principles in mind:
The file name must end with _test.go so that the code is executed when go test is executed You must have the import testing package All test case functions must begin with Test Test cases are executed in the order in which they are written in the source code Test functionsTestXxx()
The parameter is
testing.T
, we can use this type to record errors or test status
Test format:
func TestXxx (t *testing.T)
, the Xxx part can be any combination of alphanumeric characters, but the first letter cannot be a lowercase letter [ES47en-ES48en]. For example, Testintdiv is the wrong function name.
Function by call
testing.T
Error, Errorf, FailNow, Fatal, FatalIf method, indicating that the test does not pass, call Log method to record the test information.
Here is the code for our test case:
package gotest
import (
"testing"
)
func Test_Division_1(t *testing.T) {
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
t.Error(" The division function failed ") // Report an error if it is not as expected
} else {
t.Log(" The first 1 Five tests passed ") // record 1 Something you expect to record
}
}
func Test_Division_2(t *testing.T) {
t.Error(" Just won't pass ")
}
When we execute go test under the project directory, the following information is displayed:
--- FAIL: Test_Division_2 (0.00 seconds)
gotest_test.go:16: Just won't pass
FAIL
exit status 1
FAIL gotest 0.013s
From this result it appears that the test failed because in the second test function we wrote code that failed the test
t.Error
, what about the execution of our first function? Executed by default
go test
It doesn't show the information that the test passed, we need to take the parameters
go test -v
, the following information will be displayed:
=== RUN Test_Division_1
--- PASS: Test_Division_1 (0.00 seconds)
gotest_test.go:11: The first 1 Five tests passed
=== RUN Test_Division_2
--- FAIL: Test_Division_2 (0.00 seconds)
gotest_test.go:16: Just won't pass
FAIL
exit status 1
FAIL gotest 0.012s
The above output shows the process of this test in detail. We see that the test function 1Test_Division_1 tests pass, while the test function 2Test_Division_2 tests fail and conclude that the test does not pass.
Next, we change the test function 2 to the following code:
func Test_Division_2(t *testing.T) {
if _, e := Division(6, 0); e == nil { //try a unit test on function
t.Error("Division did not work as expected.") // Report an error if it is not as expected
} else {
t.Log("one test passed.", e) // record 1 Something you expect to record
}
}
And then we execute
go test -v
, the following information is displayed and the test is passed:
=== RUN Test_Division_1
--- PASS: Test_Division_1 (0.00 seconds)
gotest_test.go:11: The first 1 Five tests passed
=== RUN Test_Division_2
--- PASS: Test_Division_2 (0.00 seconds)
gotest_test.go:20: one test passed. The divisor cannot be zero 0
PASS
ok gotest 0.013s
How to write stress tests
Stress tests are used to detect the performance of functions (methods) in a similar way to how unit functional tests are written, which will not be repeated here, but note the following:
The stress test cases must follow the following format, where XXX can be any combination of alphanumeric characters, but cannot begin with a lowercase letter
func BenchmarkXXX(b *testing.B) { ... }
go test does not default to stress test functions, which require parameters to perform stress tests
-test.bench
Grammar:
TestXxx()
0
For example,
go test -test.bench=".*"
Represents all stress test functions tested
In stress test cases, remember to use in the circulatory system
testing.B.N
To enable the test to run normally
The file name must also end with _test.go
Let's create a new stress test file, es102EN_ES103en.go, with the code as follows:
package gotest
import (
"testing"
)
func Benchmark_Division(b *testing.B) {
for i := 0; i < b.N; i++ { //use b.N for looping
Division(4, 5)
}
}
func Benchmark_TimeConsumingFunction(b *testing.B) {
b.StopTimer() // Call this function to stop the stress test time count
// do 1 Some initialization work , For example, read file data , Database connection or something ,
// This time does not affect the performance of the test function itself
b.StartTimer() // Restart time
for i := 0; i < b.N; i++ {
Division(4, 5)
}
}
We execute orders
go test -test.bench=".*"
, the following results can be seen:
PASS
Benchmark_Division 500000000 7.76 ns/op
Benchmark_TimeConsumingFunction 500000000 7.80 ns/op
ok gotest 9.364s
The above results show that we did not execute any unit test functions of TestXXX, only stress test functions were executed. The first shows that Benchmark_Division executed 500,000,000 times with an average execution time of 7.76 nanoseconds. The second shows that Benchmark_TimeConsumingFunction executed 500,000,000 times with an average execution time of 7.80 nanoseconds. The last one shows the total execution time.
We execute orders
go test -test.bench=".*" -count=5
, you can see the following results: (How many executions can be specified using -ES120en)
PASS
Benchmark_Division-2 300000000 4.60 ns/op
Benchmark_Division-2 300000000 4.57 ns/op
Benchmark_Division-2 300000000 4.63 ns/op
Benchmark_Division-2 300000000 4.60 ns/op
Benchmark_Division-2 300000000 4.63 ns/op
Benchmark_TimeConsumingFunction-2 300000000 4.64 ns/op
Benchmark_TimeConsumingFunction-2 300000000 4.61 ns/op
Benchmark_TimeConsumingFunction-2 300000000 4.60 ns/op
Benchmark_TimeConsumingFunction-2 300000000 4.59 ns/op
Benchmark_TimeConsumingFunction-2 300000000 4.60 ns/op
ok _/home/diego/GoWork/src/app/testing 18.546s
go ES125en-ES126en = file name -bench=bench -cpuprofile= production cprofile file name folder
Example:
There is an popcnt folder under testBenchMark and the file popcunt_ES139en.go in popcnt
package gotest
import (
"testing"
)
func Test_Division_1(t *testing.T) {
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
t.Error(" The division function failed ") // Report an error if it is not as expected
} else {
t.Log(" The first 1 Five tests passed ") // record 1 Something you expect to record
}
}
func Test_Division_2(t *testing.T) {
t.Error(" Just won't pass ")
}
0
popcunt_test.go
package gotest
import (
"testing"
)
func Test_Division_1(t *testing.T) {
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
t.Error(" The division function failed ") // Report an error if it is not as expected
} else {
t.Log(" The first 1 Five tests passed ") // record 1 Something you expect to record
}
}
func Test_Division_2(t *testing.T) {
t.Error(" Just won't pass ")
}
1
Then run
go test -bench=".*" -cpuprofile=cpu.profile ./popcnt
package gotest
import (
"testing"
)
func Test_Division_1(t *testing.T) {
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
t.Error(" The division function failed ") // Report an error if it is not as expected
} else {
t.Log(" The first 1 Five tests passed ") // record 1 Something you expect to record
}
}
func Test_Division_2(t *testing.T) {
t.Error(" Just won't pass ")
}
2
Production of ES154en.ES155en and ES156en.ES157en documents
➜ testBenchMark ll
total 6704
drwxr-xr-x 5 diego staff 170 5 6 13:57 .
drwxr-xr-x 3 diego staff 102 5 6 11:12 ..
-rw-r--r-- 1 diego staff 5200 5 6 13:57 cpu.profile
drwxr-xr-x 3 diego staff 102 5 6 14:01 popcnt
-rwxr-xr-x 1 diego staff 3424176 5 6 13:57 popcnt.test
➜ testBenchMark
package gotest
import (
"testing"
)
func Test_Division_1(t *testing.T) {
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
t.Error(" The division function failed ") // Report an error if it is not as expected
} else {
t.Log(" The first 1 Five tests passed ") // record 1 Something you expect to record
}
}
func Test_Division_2(t *testing.T) {
t.Error(" Just won't pass ")
}
4
package gotest
import (
"testing"
)
func Test_Division_1(t *testing.T) {
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
t.Error(" The division function failed ") // Report an error if it is not as expected
} else {
t.Log(" The first 1 Five tests passed ") // record 1 Something you expect to record
}
}
func Test_Division_2(t *testing.T) {
t.Error(" Just won't pass ")
}
5
go tool pprof --web popcnt.test cpu.profile
Enter web mode
package gotest
import (
"testing"
)
func Test_Division_1(t *testing.T) {
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
t.Error(" The division function failed ") // Report an error if it is not as expected
} else {
t.Log(" The first 1 Five tests passed ") // record 1 Something you expect to record
}
}
func Test_Division_2(t *testing.T) {
t.Error(" Just won't pass ")
}
6
There are several output types available, the most useful being: --text, --web and --list. run
go tool pprof
To get the most complete list.
Let's share a point
go test
Parameter interpretation. source
Such as:
go test [-c] [-i] [build flags] [packages] [flags for test binary]
Parameter interpretation:
-ES187en: Compile go test as an executable base 2 file, but do not run tests.
-i: Installs package on which the test package depends, but does not run the tests.
For build flags, call go help build. These are the parameters that need to be used during the compilation run
For packages, call go help packages, these are for package management, 1 is normally set to empty
For flags for test binary, call go help testflag, these are the parameters that are often used during go test
-ES220en. v: Whether to print all unit test cases (whether successful or failed), not added by default, so only failed unit test cases are printed.
-ES224en. run pattern: Which unit test cases to run only
-ES229en. bench patten: Run only those performance test cases
-ES234en. benchmem: Whether to output memory during performance tests
-ES238en. benchtime t: Performance test run time, default is 1s
-ES244en. cpuprofile ES246en. out: Whether to output cpu performance analysis file or not
-ES251en. memprofile ES253en. out: Whether to output a memory performance analysis file or not
-ES257en. blockprofile ES259en. out: Whether to output a performance analysis file with internal goroutine blocking
- ES264en. memprofilerate n: Memory performance analysis has an issue of how much is allocated before hitting the record. This parameter sets the memory allocation interval for the rbi, which is the memory size represented by 1 sample in profile. The default is 512 * 1024. If you set it to 1, there will be an rbi in profile for every block allocated, and the sample generated from profile will be very large. If you set it to zero, you're not doing the rbi.
You can close memory collection by setting memprofilerate=1 and GOGC=off and observe the allocation of each block.
-ES279en.blockprofilerate n: Same as above, controls the number of nanoseconds that goroutine hits when blocking. The default setting is equivalent to -ES283en. blockprofilerate=1, and every 1 nanosecond hits a record 1
-ES287en.parallel n: The number of parallel cpu for the performance test, which is equal to GOMAXPROCS by default.
-ES294en. timeout t: Throws panic if the test case runs longer than t
-ES301en.cpu 1,2,4: On which CPU the program is running, the 1 digit in base 2 is represented, and nginx_worker_cpu_affinity
-ES311en. short: Shorten the running time of those longer running test cases
conclusion