Summary of practice on getting started with Golang unit testing

Keywords: Operation & Maintenance JSON

Background: When writing a Golang program for a long time, I didn't consciously write unit tests until I later wrote an independent project. I slowly found out how efficient and convenient it is to write unit tests for a function. Then I'll review the tests in Golang together.

UnitTest

Unit testing is a relatively efficient self-testing method for program developers to apply one piece of code to verify that another piece of code is written as expected.

Remember that when you first started operations and maintenance, you wrote programs that called specific function functions through the main program, and then subjectively validated the results with specific outputs to verify whether they met your expectations. This would be silly for orthodox software developers, but it would be useful and effective for the operations and maintenance field, as some of the operations and maintenance work is usually required.Neither hair is a more logically complex program, so there is no need to write a test program specifically to test whether another program meets expectations.

However, with the change of work content and maintenance requirements, some relevant methods in the field of formal software engineering have to be used for testing, because after long-term accumulation and summary of methods, unit testing is a better way to verify the development program and can improve the quality of program development.In the Golang language, a series of test frameworks are built in, and in addition, it mainly talks about the relevant knowledge points of UnitTest unit testing.

Writing UnitTest

Note: In Golang, there are usually some important constraints for unit testing programs, mainly as follows:

  • The unit test file name must be xxx_test.go (where XXX is the business logic program)
  • The function name of a unit test must be Testxx (xxx can be used to identify business logic functions)
  • Unit test function parameter must be t *test.T (test framework strong requirement)
  • Test program and tested program files in a package
# Sample File
# Suppose we wrote a package specifically for a piece of business logic (to initialize a rectangle and calculate volume), and you see that the overall structure is as follows
$ tree -L 2 ./unittest
./unittest
├── area.go
└── area_test.go

# Business logic code (business logic requirements and unit tests in one package)
$ cat ./unittest/area.go
package unittest

type box struct {
    length  int
    width   int
    height  int
    name    string
}

// Initialize a structure pointer object, then use the structure pointer method to set and get object properties
func Newbox() (*box) {
    return &box{}
}

// Set specific properties (name, size of specification) for the structure object
// Note: In the following methods, the method acceptor is the pointer type and the method parameter is the value type, so there may be confusion when assigning values, where the underlying Golang optimization is done (v.name = name equals (*v).name = name)
func (v *box) SetName(name string) {
    v.name = name
}
func (v *box) SetSize(l,w,h int) {
    v.length = l
    v.width = w
    v.height = h
}

// Get some properties (name and volume) of the object
func (v *box) GetName() (string) {
    return v.name
}
func (v *box) GetVolume() (int) {
    return (v.length)*(v.width)*(v.height)
}

# Unit test logic corresponding to business logic
$ cat unittest/area_test.go
package unittest
// The testing module must be imported, and the recipient of the method is (t *testing.T)
import (
    "fmt"
    "testing"
)
// Test 1: Test whether the name meets expectations
func TestSetSomething(t *testing.T) {
    box := Newbox()
    box.SetName("bgbiao")
    if box.GetName() == "bgbiao" {
        fmt.Println("the rectangular name's result is ok")
    }
}
// Test 2: Test whether the calculated volume meets expectations
func TestGetSomething(t *testing.T) {
    box := Newbox()
    box.SetSize(3,4,5)
    if box.GetVolume() == 60 {
        fmt.Println("the rectangular volume's result is ok")
    }
}

# Run unit test program
# You can see that both of the unit tests we wrote were tested as expected
$ cd unittest
$ go test
the rectangular name's result is ok
the rectangular volume's result is ok
PASS
ok  	_/User/BGBiao/unittest	0.005s

Running unit tests

From the test example above, we all know that you can use go test to test Golang code. Next, let's explain some of the other uses of go test (the rules above can also be found in the go help test help documentation).

Here is a summary of several commonly used parameters:

  • -args: Specify some parameters for testing (you can specify timeouts, cpu bindings, maneuvers, etc. (go test contains unit tests, pressure tests, etc.)

    • -test.v: Whether to output all unit test cases (success or failure), which are not added by default, so only 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 testing
    • -test.benchtime: Performance test run time, default is 1s
    • -test.cpuprofile cpu.out: Whether to output the CPU performance analysis file
    • -test.memprofile mem.out: Whether to output a memory performance analysis file
    • -test.blockprofile block.out: Output performance analysis file for internal goroutine blocking
  • -c: Compile the test file to pkg.test, but do not run the test program

  • -exec xprog: Run the compiled test file with xprog parameters (parameters similar to those after go run)

  • -i: Install dependent packages in the test program, but do not run the test program

  • -json: Output test results in JSON format

  • -o file: Specifies the name of the file generated after the test program is compiled

Common command parameters in unit tests:

# Run tests on all unit test programs in the current directory (that is, all function s in all xxx_test.go files run)
$ go test
the rectangular name's result is ok
the rectangular volume's result is ok
PASS
ok  	_/Users/BGBiao/unittest	0.005s

# View detailed unit test results
# (go test-v equals go test-args-test.v)
$ go test -v
=== RUN   TestSetSomething
the rectangular name's result is ok
--- PASS: TestSetSomething (0.00s)
=== RUN   TestGetSomething
the rectangular volume's result is ok
--- PASS: TestGetSomething (0.00s)
PASS
ok  	_/Users/BGBiao/unittest	0.005s

# Specify unit test function for testing
# go test -v -run functionname
$ go test -v -test.run TestGetSomething
=== RUN   TestGetSomething
the rectangular volume's result is ok
--- PASS: TestGetSomething (0.00s)
PASS
ok  	_/Users/BGBiao/unittest	0.005s

Unit test considerations

Note: In unit testing, one of the most important things is how to construct test data, because the test data we can usually think of is expected, and some core logic test data can not be considered, so the following aspects can be considered when constructing test data:

    1. Normal Input: Normal predictable test cases
    1. Boundary Input: Input in extreme cases to test fault tolerance
    1. Illegal Input: Input exception data type, whether the entire logic can be properly handled or captured
    1. White box overrides: Test cases that need to be designed can cover all code (statement overrides, conditional overrides, branch overrides, branch/conditional overrides, conditional combination overrides)

Note: When writing a project, it is important to test the logic code of the underlying tool layer util in an all-round and multi-scenario way. Otherwise, when the project is large, referencing it everywhere can cause a lot of trouble. Secondly, our code logic is usually updated iteratively, and the unit test code should also be updated regularly. wx Public: BGBiao, progress together~

Posted by MrNonchalant on Sat, 09 Nov 2019 16:51:58 -0800