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 functions TestXxx() 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


Related articles: