SoFunction
Updated on 2025-03-01

Teach you step by step to write testable Go code

The first test “Hello Test!”

First, create the hello directory under our $GOPATH/src directory as the root directory of all the sample code involved in this article.

Then, create a new file named and define a functionhello() , the function is to return a sentence spliced ​​by several words:

package hello

func hello() string {
 words := []string{"hello", "func", "in", "package", "hello"}
 wl := len(words)

 sentence := ""
 for key, word := range words {
  sentence += word
  if key < wl-1 {
   sentence += " "
  } else {
   sentence += "."
  }
 }
 return sentence
}

Next, create a new file named hello_test.go and fill in the following content:

package hello

import (
 "fmt"
 "testing"
)

func TestHello(t *) {
 got := hello()
 expect := "hello func in package hello."

 if got != expect {
  ("got [%s] expected [%s]", got, expect)
 }
}

func BenchmarkHello(b *) {
 for i := 0; i < ; i++ {
  hello()
 }
}

func ExampleHello() {
 hl := hello()
 (hl)
 // Output: hello func in package hello.
}

Finally, open the terminal, enter the hello directory, and entergo testCommand and press Enter, you can see the following output:

PASS
ok  hello 0.007s

Writing test code

Golang's test code is located in the source file with the name ending with _test.go in the source code of a package. The test code includes test functions, test auxiliary code and example functions. The test functions include functional test functions starting with Test and performance test functions starting with Benchmark. The test auxiliary code is a public function, initialization function, test data, etc. serving the test function, and the example function is a function that starts with Example that explains the usage of the function being tested.

In most cases, the test code is as part of a package, meaning it can access elements that are not exportable in the package. However, when needed (such as avoiding circular dependencies), you can also modify the package name of the test file, such as the test file of package hello, and the package name can be set to package hello_test.

Functional test function

Functional testing functions need to receive a single parameter t of type *, which is used to manage test status and support formatted test logs. The test logs are accumulated during the test execution and are output to the standard error output after completion.

Here are the usages of common methods of types excerpted from the Go standard library:

When the execution result of a test case in the test function does not match the expected result, it is called()or()Method logs and marks test failure

# /usr/local/go/src/bytes/compare_test.go
func TestCompareIdenticalSlice(t *) {
 var b = []byte("Hello Gophers!")
 if Compare(b, b) != 0 {
  ("b != b")
 }
 if Compare(b, b[:1]) != 1 {
  ("b > b[:1] failed")
 }
}

use()and()Method: After a test case fails, the test function will be popped up.

# /usr/local/go/src/bytes/reader_test.go
func TestReadAfterBigSeek(t *) {
 r := NewReader([]byte("0123456789"))
 if _, err := (1<<31+5, os.SEEK_SET); err != nil {
  (err)
 }
 if n, err := (make([]byte, 10)); n != 0 || err !=  {
  ("Read = %d, %v; want 0, EOF", n, err)
 }
}

use()and()Method, skip the execution of a test case

# /usr/local/go/src/archive/zip/zip_test.go
func TestZip64(t *) {
 if () {
  ("slow test; skipping")
 }
 const size = 1 << 32 // before the "END\n" part
 buf := testZip64(t, size)
 testZip64DirectoryRecordLength(buf, t)
}

Passed during the execution of test cases()and()Logging

# /usr/local/go/src/regexp/exec_test.go
func TestFowler(t *) {
 files, err := ("testdata/*.dat")
 if err != nil {
  (err)
 }
 for _, file := range files {
  (file)
  testFowler(t, file)
 }
}

use()Mark test functions that need to be executed concurrently

# /usr/local/go/src/runtime/stack_test.go
func TestStackGrowth(t *) {
 ()
 var wg 

 // in a normal goroutine
 (1)
 go func() {
  defer ()
  growStack()
 }()
 ()

 // ...
}

Performance Test Function

The performance test function needs to receive a single parameter b of type *, and the performance test function needs to be called cycled in the performance test function. Types are used to manage test time and iteration run times, and also support the same way to manage test status and formatted test logs. The difference is that the logs will always be output.

Here are the usages of common methods of types excerpted from the Go standard library:

Called in a function() , enable memory usage analysis

# /usr/local/go/src/bufio/bufio_test.go
func BenchmarkWriterFlush(b *) {
 ()
 bw := NewWriter()
 str := ("x", 50)
 for i := 0; i < ; i++ {
  (str)
  ()
 }
}

pass() () ()to stop, reset, start time elapse and memory allocation count

# /usr/local/go/src/fmt/scan_test.go
func BenchmarkScanInts(b *) {
 ()
 ints := makeInts(intCount)
 var r RecursiveInt
 for i :=  - 1; i >= 0; i-- {
  buf := (ints)
  ()
  scanInts(&r, buf)
  ()
 }
}

Call()Record the number of bytes processed in an operation

# /usr/local/go/src/testing/
func BenchmarkFields(b *) {
 (int64(len(fieldsInput)))
 for i := 0; i < ; i++ {
  Fields(fieldsInput)
 }
}

pass()Methods and * typesNext()Method to execute the object to be tested concurrently

# /usr/local/go/src/sync/atomic/value_test.go
func BenchmarkValueRead(b *) {
 var v Value
 (new(int))
 (func(pb *) {
  for () {
   x := ().(*int)
   if *x != 0 {
    ("wrong value: got %v, want 0", *x)
   }
  }
 })
}

Test auxiliary code

Test-assisted code is caused by code reuse and code quality considerations during the writing of test code. It mainly includes the following aspects:

Introduce dependable external packages, such as testing packages required for each test file:

# /usr/local/go/src/log/log_test.go:
import (
 "bytes"
 "fmt"
 "os"
 "regexp"
 "strings"
 "testing"
 "time"
)

Define constants and variables used multiple times, test case data, etc.:

# /usr/local/go/src/log/log_test.go:
const (
 Rdate   = `[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]`
 Rtime   = `[0-9][0-9]:[0-9][0-9]:[0-9][0-9]`
 Rmicroseconds = `\.[0-9][0-9][0-9][0-9][0-9][0-9]`
 Rline   = `(57|59):` // must update if the calls to  /  below move
 Rlongfile  = `.*/[A-Za-z0-9_\-]+\.go:` + Rline
 Rshortfile = `[A-Za-z0-9_\-]+\.go:` + Rline
)

// ...

var tests = []tester{
 // individual pieces:
 {0, "", ""},
 {0, "XXX", "XXX"},
 {Ldate, "", Rdate + " "},
 {Ltime, "", Rtime + " "},
 {Ltime | Lmicroseconds, "", Rtime + Rmicroseconds + " "},
 {Lmicroseconds, "", Rtime + Rmicroseconds + " "}, // microsec implies time
 {Llongfile, "", Rlongfile + " "},
 {Lshortfile, "", Rshortfile + " "},
 {Llongfile | Lshortfile, "", Rshortfile + " "}, // shortfile overrides longfile
 // everything at once:
 {Ldate | Ltime | Lmicroseconds | Llongfile, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rlongfile + " "},
 {Ldate | Ltime | Lmicroseconds | Lshortfile, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rshortfile + " "},
}

Like ordinary Golang source code, the init function can also be defined in the test code. The init function will be automatically called after introducing external packages, defining constants, and declaring variables. You can write test-related initialization code in the init function.

# /usr/local/go/src/bytes/buffer_test.go
func init() {
 testBytes = make([]byte, N)
 for i := 0; i < N; i++ {
  testBytes[i] = 'a' + byte(i%26)
 }
 data = string(testBytes)
}

Encapsulate common functions dedicated to testing, structures dedicated to abstract testing, etc.:

# /usr/local/go/src/log/log_test.go:
type tester struct {
 flag int
 prefix string
 pattern string // regexp that log output must match; we add ^ and expected_text$ always
}

// ...

func testPrint(t *, flag int, prefix string, pattern string, useFormat bool) {
 // ...
}

Sample Function

The sample function does not need to receive parameters, but requires the commented Output: mark to illustrate the output value of the sample function. The sample function without specifying the Output: mark or the output value is empty will not be executed.

The example function needs to be a method that belongs to a package/function/type/type. The specific naming rules are as follows:

func Example() { ... }  # package example functionfunc ExampleF() { ... }  # Example function of function Ffunc ExampleT() { ... }  # Example function of type Tfunc ExampleT_M() { ... } # Example function of M method of type T
# Multi-example function requires an underscore and a suffix starting with lowercase lettersfunc Example_suffix() { ... }
func ExampleF_suffix() { ... }
func ExampleT_suffix() { ... }
func ExampleT_M_suffix() { ... }

The go doc tool parses the function body of the example function as the usage of the corresponding package/function/type/type method.

For relevant instructions for testing functions, you can use go help testfunc to view the help document.

Use the go test tool

In Golang, go test is used to execute the test code, open the shell terminal, enter the directory where the package that needs to be tested is located to execute go test, or execute it directly.go test $pkg_name_in_gopathTests can be performed on the specified package.

Through the shapego test /tabalt/...The command can be executed$GOPATH//tabalt/Tests of all projects in the directory.go test stdThe command can execute all tests of the Golang standard library.

If you want to see which test functions are executed and the execution results of the functions, you can use the -v parameter:

[tabalt@localhost hello] go test -v
=== RUN TestHello
--- PASS: TestHello (0.00s)
=== RUN ExampleHello
--- PASS: ExampleHello (0.00s)
PASS
ok  hello 0.006s

Suppose we have many functional test functions, but a certain test only wants to execute some of them. You can use the -run parameter and use a regular expression to match the name of the functional test function to be executed. If the parameters are specified below, the function test function TestHello will not be executed.

[tabalt@localhost hello] go test -v -run=xxx
PASS
ok  hello 0.006s

The performance test function will not be executed by default. You need to add the -bench parameter and specify a regular expression that matches the performance test function name; for example, if you want to execute all the performance test functions in a certain package, you can add the parameter -bench . or -bench=.

[tabalt@localhost hello] go test -bench=.
PASS
BenchmarkHello-8  2000000   657 ns/op
ok  hello 1.993s

To view the memory status during performance testing, you can add the parameter -benchmem:

[tabalt@localhost hello] go test -bench=. -benchmem
PASS
BenchmarkHello-8  2000000   666 ns/op   208 B/op   9 allocs/op
ok  hello 2.014s

Parameters -cover can be used to view the coverage of the code of the tests we wrote:


Detailed coverage information can be output to the file through -coverprofile and used go tool cover to view it. Please refer to the usagego tool cover -help

Morego testThe parameters and usage of the command can be passedgo help testflagCome to view the help documentation.

Advanced testing technology

IO related tests

The commonly used error reader and Writer are implemented in the testing/iotest package, which can be used in io-related tests. Mainly:

Trigger data error dataErrReader, throughDataErrReader()Function creation

halfReader that reads half content, throughHalfReader()Function creation

Read a byte oneByteReader, throughOneByteReader()Function creation

Trigger timeoutReader with timeout error, throughTimeoutReader()Function creation

The truncateWriter that stops after writing the specified number of digits, passTruncateWriter()Function creation

ReadLogger that records logs when reading, throughNewReadLogger()Function creation

WriteLogger that records logs when writing, throughNewWriteLogger()Function creation

Black box test

The testing/quick package implements practical functions Check and CheckEqual to help black box testing.

The first parameter of the Check function is the black box function f that only returns the bool value to be tested. Check will set any value for each parameter of f and call it multiple times. If f returns false, the Check function will return the error value *CheckError. The second parameter of the Check function can specify a type of config, and the nil will be used by default. The structure contains options for test run.

# /usr/local/go/src/math/big/int_test.go
func checkMul(a, b []byte) bool {
 var x, y, z1 Int
 (a)
 (b)
 (&x, &y)

 var z2 Int
 (mulBytes(a, b))

 return (&z2) == 0
}

func TestMul(t *) {
 if err := (checkMul, nil); err != nil {
  (err)
 }
}

The CheckEqual function compares whether the given two black box functions are equal. The function prototype is as follows:

func CheckEqual(f, g interface{}, config *Config) (err error)

HTTP testing

The net/http/httptest package provides HTTP-related code tools. Our test code can create a temporary code to test the HTTP request:

ts := ((func(w , r *) {
 (w, "Hello, client")
}))
defer ()

res, err := ()
if err != nil {
 (err)
}

greeting, err := ()
()
if err != nil {
 (err)
}

("%s", greeting)

You can also create a reply recorderTo detect the response content:

handler := func(w , r *) {
 (w, "something failed", )
}

req, err := ("GET", "/foo", nil)
if err != nil {
 (err)
}

w := ()
handler(w, req)

("%d - %s", , ())

Test process operation behavior

When our tested function has the behavior of operating processes, we can perform tests using the tested program as a child process. Here is an example:

//The process being tested exits the functionfunc Crasher() {
 ("Going down in flames!")
 (1)
}

//Test function of test process exit functionfunc TestCrasher(t *) {
 if ("BE_CRASHER") == "1" {
  Crasher()
  return
 }
 cmd := ([0], "-=TestCrasher")
  = append((), "BE_CRASHER=1")
 err := ()
 if e, ok := err.(*); ok &amp;&amp; !() {
  return
 }
 ("process ran with err %v, want exit status 1", err)
}

Summarize

The above is the entire content of this article. I hope the content of this article will be helpful to everyone to learn or use Go. If you have any questions, you can leave a message to communicate.