In depth understanding of unit test and performance test of golang

Time:2020-2-26

Preface

You should know that the most important thing in developing programs is testing. How to ensure the quality of the code, how to ensure that each function can be run, the running result is correct, and how to ensure that the written code performance is good? We know that the focus of unit testing is to find the logic errors in the program design or implementation, so that the problems can be exposed as early as possible The key point of performance test is to find out some problems in program design, so that online programs can remain stable in the case of high concurrency. This section will take this series of questions to explain how to implement unit test and performance test in go language.

There is a lightweight testing framework testing and a built-in go in the go language Test command is used to implement unit test and performance test. The testing framework is similar to that in other languages. You can write test cases for corresponding functions based on this framework, or write corresponding stress test cases based on this framework. Let’s take a look at how to write.

How to write test cases

Becausego testThe command can only execute all files in a corresponding directory, so let’s create a new project directory, gotest, so that all our code and test code are in this directory.

Next, we create two files under the directory: go test.go and gotest_test.go

1. Gotest.go: in this file, we have created a package, in which a function implements division operation:

package gotest

import (
 "errors"
)

func Division(a, b float64) (float64, error) {
 if b == 0 {
 Return 0, errors.new ("divisor cannot be 0")
 }

 return a / b, nil
}

2. Go test_test.go: This is our unit test file, but remember the following principles:

  • The file name must be the end of_test.go, so that the corresponding code will be executed when go test is executed
  • You have to import testing
  • All test case functions must start with test
  • Test samples are executed in the order in which they are written in the source code
  • Test functionTestXxx()The parameters aretesting.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 alphanumeric combination, but the initial cannot be a lowercase letter [A-Z]. For example, testintdiv is the wrong function name.
  • Function by callingtesting.TThe error, errorf, failnow, fatal, fatalif methods of indicate that the test fails. The log method is called 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 ("division function test failed") // if it is not as expected, an error will be reported
 } else {
 t. Log ("the first test passed") // record some information you expect to record
 }
}

func Test_Division_2(t *testing.T) {
 t. Error ("no pass")
}

When we execute go test under the project directory, the following information will be displayed:

--- FAIL: Test_Division_2 (0.00 seconds)
 Go test_test.go: 16: no
FAIL
exit status 1
FAIL gotest 0.013s

From this result, we can see that the test failed, because in the second test function, we wrote the code that failed the testt.Error, what about the execution of our first function? Execute by defaultgo testIt will not display the information of passing the test. We need to bring the parametersgo test -v, the following message is displayed:

=== RUN Test_Division_1
--- PASS: Test_Division_1 (0.00 seconds)
 Go test_test.go:11: the first test passed
=== RUN Test_Division_2
--- FAIL: Test_Division_2 (0.00 seconds)
 Go test_test.go: 16: no
FAIL
exit status 1
FAIL gotest 0.012s

The above output shows the process of this test in detail. We can see that the test function 1Test? Division? 1 passed the test, and the test function 2test? Division? 2 failed the test. Finally, we conclude that the test failed.

Next, we modify 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.") // if not, an error will be reported
 } else {
 t. Log ("one test passed.", e) // record some information you want to record
 }
}

And then we dogo test -v, the following information is displayed and the test is passed:

=== RUN Test_Division_1
--- PASS: Test_Division_1 (0.00 seconds)
 Go test_test.go:11: the first test passed
=== RUN Test_Division_2
--- PASS: Test_Division_2 (0.00 seconds)
 Go test_test.go: 20: one test passed. Divisor cannot be 0
PASS
ok gotest 0.013s

How to write stress tests

Pressure test is used to test the performance of function (method). It is similar to the method of writing unit function test. It will not be repeated here, but the following points need to be noted:

  • The stress test case must follow the following format, where XXX can be any alphanumeric combination, but the initial cannot be a lowercase letter
    
    func BenchmarkXXX(b *testing.B) { ... }
  • Go test does not default to the function that performs stress test. If you want to perform stress test, you need to bring parameters-test.benchSyntax:-test.bench="test_name_regex",for examplego test -test.bench=".*"Represents all the stress test functions of the test
  • In a stress test case, remember to use it inside the looptesting.B.NSo that the test can run normally
  • The filename must also end with ‘ou test.go’

Next, we create a new stress test file, webbench test.go. The code is 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 time count of stress test

 //Do some initialization work, such as reading file data, database connection, etc,
 //So these times don't affect the performance of our test function

 b. Starttimer() // restart time
 for i := 0; i < b.N; i++ {
 Division(4, 5)
 }
}

We carry out the ordergo test -test.bench=".*", you can see the following results:


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 have not executed any unit test functions of testXXX, and only the stress test functions are executed in the displayed results. The first one shows that the benchmark “division has executed 500000000 times, with an average execution time of 7.76 nanoseconds each time. The second one shows that the benchmark” timeconsuming function has executed 500000000 times, with an average execution time of 7.80 nanoseconds each time. The last bar shows the total execution time.

We carry out the ordergo test -test.bench=".*" -count=5, you can see the following results: (use – count to specify how many times)


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 test – run = file name – bench = bench name – cpuprofile = production cprofile file name folder

Example:

There is a popcnt folder under testbenchmark, and the file popcunt_test.go is in popcnt


➜ testBenchMark ls
popcnt

Price inquiry content of poppunt_test.go:


ackage popcnt

import (
 "testing"
)

const m1 = 0x5555555555555555
const m2 = 0x3333333333333333
const m4 = 0x0f0f0f0f0f0f0f0f
const h01 = 0x0101010101010101

func popcnt(x uint64) uint64 {
 x -= (x >> 1) & m1
 x = (x & m2) + ((x >> 2) & m2)
 x = (x + (x >> 4)) & m4
 return (x * h01) >> 56
}

func BenchmarkPopcnt(b *testing.B) {
 for i := 0; i < b.N; i++ {
  x := i
  x -= (x >> 1) & m1
  x = (x & m2) + ((x >> 2) & m2)
  x = (x + (x >> 4)) & m4
  _ = (x * h01) >> 56
 }
}

Then rungo test -bench=".*" -cpuprofile=cpu.profile ./popcnt


➜ testBenchMark go test -bench=".*" -cpuprofile=cpu.profile ./popcnt
testing: warning: no tests to run
PASS
BenchmarkPopcnt-8 1000000000    2.01 ns/op
ok  app/testBenchMark/popcnt 2.219s
➜ 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 4 diego staff  136 5 6 11:47 popcnt
-rwxr-xr-x 1 diego staff 3424176 5 6 13:57 popcnt.test
➜ testBenchMark

Production cpu.profile price inquiry and popcnt.test file


➜ 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
Go tool pprof popcnt.test cpu.profile enter interactive mode

➜ testBenchMark go tool pprof popcnt.test cpu.profile
Entering interactive mode (type "help" for commands)
(pprof) top
1880ms of 1880ms total ( 100%)
  flat flat% sum%  cum cum%
 1790ms 95.21% 95.21%  1790ms 95.21% app/testBenchMark/popcnt.BenchmarkPopcnt
  90ms 4.79% 100%  90ms 4.79% runtime.usleep
   0  0% 100%  1790ms 95.21% runtime.goexit
   0  0% 100%  90ms 4.79% runtime.mstart
   0  0% 100%  90ms 4.79% runtime.mstart1
   0  0% 100%  90ms 4.79% runtime.sysmon
   0  0% 100%  1790ms 95.21% testing.(*B).launch
   0  0% 100%  1790ms 95.21% testing.(*B).runN
(pprof)

go tool pprof --web popcnt.test cpu.profileEnter web mode


$ go tool pprof --text mybin http://myserver:6060:/debug/pprof/profile

There are several output types available, the most useful of which are: – text, — web and — list. Functiongo tool pprof To get the most complete list.

Let’s share a littlego testParameter interpretation of. source

The format is as follows:

go test [-c] [-i] [build flags] [packages] [flags for test binary]

Parameter interpretation:

-C: compile go test as an executable binary, but do not run the test.

-I: install the package on which the test package depends, but do not run the test.

For build flags, call go help build. These are the parameters that need to be used during compilation and operation. Generally, they are set to null

About packages, call go help packages. These are about package management, which is generally set to empty

About flags for test binary, call go help testFlag. These are the parameters often used in go test

-Test. V: whether to output all unit test cases (success or failure or not) is not added by default, so only the failed unit test cases are output.

-Test.run pattern: which unit test cases to run only

-Test.bench patten: run only those performance test cases

-Test.benchmem: whether to output memory during performance test

-Test.benchtime T: the time when the performance test runs. The default is 1s

-Test.cpuprofile cpu.out: output CPU performance analysis file or not

-Test.memprofile mem.out: output memory performance analysis file or not

-Test.blockprofile block.out: output performance analysis file blocked by internal goroutine

-Test.memprofilerate n: during memory performance analysis, there is a problem of how much memory is allocated before recording. This parameter is used to set the memory allocation interval, which is the memory size represented by a sample in the profile. The default setting is 512 * 1024. If you set it to 1, every memory block allocated will have a dot in the profile, and there will be a lot of samples in the generated profile. If you set it to 0, you don’t need to do the dot.

You can turn off memory recycling by setting memprofile = 1 and gogc = off, and observe the allocation of each memory block.

-Test.blockprofilerate n: basically the same as above. It controls the number of nanoseconds when goroutine is blocked. If it is not set by default, it is equivalent to – test. Blockprofilerate = 1. Record every nanosecond

-Test.parallel n: the number of program parallel CPUs for performance test, which is equal to gomaxprocs by default.

-Test.timeout T: if the test case runs for more than t, a panic is thrown

-Test.cpu 1,2,4: which CPUs the program runs on, which is represented by binary 1 in place, and which is the same reason as nginx ﹣ worker ﹣ CPU ﹣ affinity of nginx

-Test.short: reduce the running time of test cases with longer running time

summary

The above is the whole content of this article. I hope that the content of this article can bring some help to your study or work. If you have any questions, you can leave a message and communicate with us. Thank you for your support for developpaer.

Recommended Today

Python basics Chinese series tutorial · translation completed

Original: Python basics Python tutorial Protocol: CC by-nc-sa 4.0 Welcome anyone to participate and improve: a person can go very fast, but a group of people can go further. Online reading Apache CN learning resources catalog introduce Seven reasons to learn Python Why Python is great Learn Python introduction Executing Python scripts variable character string […]