Write a testable go code

Source: Internet
Author: User
Tags benchmark
This is a creation in Article, where the information may have evolved or changed.

Original link: Http://tabalt.net/blog/golang ...

Golang, as a advertised language, provides a very simple and practical ability to write unit tests. This article uses the Golang source package to learn how to write testable go codes in real-world projects.

The first test of "Hello test!"

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

Then, create a new file named Hello.go, define a function hello (), function is to return a number of words to be stitched into a sentence:

package hellofunc 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:

package helloimport (    "fmt"    "testing")func TestHello(t *testing.T) {    got := hello()    expect := "hello func in package hello."    if got != expect {        t.Errorf("got [%s] expected [%s]", got, expect)    }}func BenchmarkHello(b *testing.B) {    for i := 0; i < b.N; i++ {        hello()    }}func ExampleHello() {    hl := hello()    fmt.Println(hl)    // Output: hello func in package hello.}

Finally, open the terminal, enter the Hello directory, enter go test the command and return, you can see the following output:

PASSok      hello  0.007s

Writing test Code

The test code for Golang is located in the source file with the name of the package's source code, and the test _test.go code contains the test function, the test helper code, and the sample function, and the test function has a functional test function starting with test and a performance test function starting with benchmark. The test helper code is a common function for testing functions, initialization functions, test data, and so on, and the example function is the function that is used to test the function using example.

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

function Test function

The function test function requires *testing.T a single parameter t,testing of the receiving type. The type T is used to manage test status and to support formatted test logs. The test log accumulates during the test execution and outputs to the standard error output after completion.

The following is a excerpt from the Go Standard library testing. Usage of common methods of type T:

    • T is called when a test case execution in a test function does not match the expected result. Error () or T. Errorf () method logs the log and marks a test failure

# /usr/local/go/src/bytes/compare_test.gofunc TestCompareIdenticalSlice(t *testing.T) {    var b = []byte("Hello Gophers!")    if Compare(b, b) != 0 {        t.Error("b != b")    }    if Compare(b, b[:1]) != 1 {        t.Error("b > b[:1] failed")    }}
    • Use T. Fatal () and T. Fatalf () method to jump out of a test case after it has failed

# /usr/local/go/src/bytes/reader_test.gofunc TestReadAfterBigSeek(t *testing.T) {    r := NewReader([]byte("0123456789"))    if _, err := r.Seek(1<<31+5, os.SEEK_SET); err != nil {        t.Fatal(err)    }    if n, err := r.Read(make([]byte, 10)); n != 0 || err != io.EOF {        t.Errorf("Read = %d, %v; want 0, EOF", n, err)    }}
    • Use T. Skip () and T. Skipf () method, skipping execution of a test case

# /usr/local/go/src/archive/zip/zip_test.gofunc TestZip64(t *testing.T) {    if testing.Short() {        t.Skip("slow test; skipping")    }    const size = 1 << 32 // before the "END\n" part    buf := testZip64(t, size)    testZip64DirectoryRecordLength(buf, t)}
    • The process of executing the test case passes through T. Log () and T. LOGF () logging

# /usr/local/go/src/regexp/exec_test.gofunc TestFowler(t *testing.T) {    files, err := filepath.Glob("testdata/*.dat")    if err != nil {        t.Fatal(err)    }    for _, file := range files {        t.Log(file)        testFowler(t, file)    }}
    • Use T. Parallel () marks a test function that requires concurrent execution

# /usr/local/go/src/runtime/stack_test.gofunc TestStackGrowth(t *testing.T) {    t.Parallel()    var wg sync.WaitGroup    // in a normal goroutine    wg.Add(1)    go func() {        defer wg.Done()        growStack()    }()    wg.Wait()    // ...}

Performance Test function

The performance test function requires *testing.B a single parameter B of the receiving type, and loop B is required in the performance test function. n times the measured function is called. Testing. The B type is used to manage the test time and iteration run times, and also supports and testing. t the same way to manage the test state and format the test log, unlike the testing. B's log is always output.

The following is a excerpt from the Go Standard library testing. Usage of common methods of type B:

    • Call T in the function. Reportallocs (), enabling Memory usage analysis

# /usr/local/go/src/bufio/bufio_test.gofunc BenchmarkWriterFlush(b *testing.B) {    b.ReportAllocs()    bw := NewWriter(ioutil.Discard)    str := strings.Repeat("x", 50)    for i := 0; i < b.N; i++ {        bw.WriteString(str)        bw.Flush()    }}
    • Stop, reset, start time elapsed, and memory allocation count with B.stoptimer (), B.resettimer (), B.starttimer ()

# /usr/local/go/src/fmt/scan_test.gofunc BenchmarkScanInts(b *testing.B) {    b.ResetTimer()    ints := makeInts(intCount)    var r RecursiveInt    for i := b.N - 1; i >= 0; i-- {        buf := bytes.NewBuffer(ints)        b.StartTimer()        scanInts(&r, buf)        b.StopTimer()    }}
    • Call B. SetBytes () records the number of bytes processed in an operation

# /usr/local/go/src/testing/benchmark.gofunc BenchmarkFields(b *testing.B) {    b.SetBytes(int64(len(fieldsInput)))    for i := 0; i < b.N; i++ {        Fields(fieldsInput)    }}
    • Through B. Runparallel () method and *testing. PB-Type Next () method to execute the object under test concurrently

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

Test Helper Code

The test helper code is generated by code reuse and code quality considerations during the writing of the test code. Mainly include the following aspects:

    • Introduce dependent external packages, such as testing packages that are required for each test file:

# /usr/local/go/src/log/log_test.go:import (    "bytes"    "fmt"    "os"    "regexp"    "strings"    "testing"    "time")
    • Defines constants and variables that are used multiple times, test case data, and so on:

#/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 L.printf/l.print below move Rlongfile = '. */[ A-za-z0-9_\-]+\.go: ' + rline rshortfile = ' [a-za-z0-9_\-]+\.go: ' + rline]//... var tests = []tester{//Individua L 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 normal Golang source code, the INIT function can be defined in the test code, and the INIT function is automatically invoked after the introduction of an external package, defining constants, declaring variables, and writing test-related initialization code in the INIT function.

# /usr/local/go/src/bytes/buffer_test.gofunc init() {    testBytes = make([]byte, N)    for i := 0; i < N; i++ {        testBytes[i] = 'a' + byte(i%26)    }    data = string(testBytes)}
    • Encapsulation test-specific public functions, abstract test-specific structures, 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 *testing.T, flag int, prefix string, pattern string, useFormat bool) {    // ...}

Example functions

The sample function does not need to receive parameters, but it needs to use the annotation's Output: markup to say the output value of the example function, and an example function that does not specify a Output: tag or an empty output value is not executed.

The example function requires a method that belongs to a package/function/type/type, with the following naming conventions:

func Example() { ... }      # 包的示例函数func ExampleF() { ... }     # 函数F的示例函数func ExampleT() { ... }     # 类型T的示例函数func ExampleT_M() { ... }   # 类型T的M方法的示例函数# 多示例函数 需要跟下划线加小写字母开头的后缀func Example_suffix() { ... }func ExampleF_suffix() { ... }func ExampleT_suffix() { ... }func ExampleT_M_suffix() { ... }

The Go Doc tool parses the function body of the sample function as the method of the corresponding Package/function/type/type.

A description of the test function, which can be used go help testfunc to view the help document.

Using the Go Test tool

Golang executes the test code through the command-line tool go test , opens the shell terminal, enters the directory where the package that needs to be tested executes go test , or executes the go test $pkg_name_in_gopath test directly on the specified package.

go test github.com/tabalt/...you can perform $GOPATH/github.com/tabalt/ tests on all items in the directory by using the form-like command. go test stdcommand to perform all tests of the Golang standard library.

If you want to see what test functions and function execution results you have performed, you can use -v parameters:

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

Suppose we have a lot of functional test functions, but a test just wants to do some of them, you can use a regular expression to match the function test function name to be executed by-run parameter. The function test function is not executed until the parameter is specified below TestHello .

[tabalt@localhost hello] go test -v -run=xxxPASSok      hello  0.006s

The performance test function does not execute by default, you need to add the-bench parameter, and you specify a regular expression that matches the performance test function name, for example, to execute all the performance test functions in a package to add parameters -bench . or -bench=. .

[tabalt@localhost hello] go test -bench=.PASSBenchmarkHello-8     2000000           657 ns/opok      hello  1.993s

To see the memory of the performance test, you can add the parameters -benchmem :

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

Parameters -cover can be used to view the coverage of the code we have written for the test:

[tabalt@localhost hello] go test -coverPASScoverage: 100.0% of statementsok      hello  0.006s

Detailed coverage information can be -coverprofile exported to a file and used go tool cover to view, usage please refer to go tool cover -help .

For more go test command parameters and usage, you can go help testflag view the help documentation.

Advanced testing Technology

IO related tests

Common error reader and writer are implemented in the Testing/iotest package for use in IO-related tests. Mainly include:

    • Trigger data error Dataerrreader, created by Dataerrreader () function

    • Read half the contents of the Halfreader, created by the Halfreader () function

    • Reads a byte onebytereader, created by the Onebytereader () function

    • Timeoutreader that triggers a time-out error, created by the Timeoutreader () function

    • Truncatewriter, which is stopped after writing the specified number of bits, is created by the Truncatewriter () function

    • Log Readlogger on read, created by Newreadlogger () function

    • Writelogger logging on write, created by Newwritelogger () function

Black box test

The Testing/quick package implements a utility check and checkequal that help with black-box testing.

The 1th parameter of the check function is the black box function that returns a bool value to be tested F,check will set an arbitrary value for each parameter of F and is called multiple times, and if F returns the False,check function returns the error value *checkerror. The 2nd parameter of a check function can specify a quick. Config, which will use Quick.defaultconfig by default. Quick. The config structure body contains the options for the test run.

# /usr/local/go/src/math/big/int_test.gofunc checkMul(a, b []byte) bool {    var x, y, z1 Int    x.SetBytes(a)    y.SetBytes(b)    z1.Mul(&x, &y)    var z2 Int    z2.SetBytes(mulBytes(a, b))    return z1.Cmp(&z2) == 0}func TestMul(t *testing.T) {    if err := quick.Check(checkMul, nil); err != nil {        t.Error(err)    }}

The Checkequal function is to compare the given two black box functions for equality, the function prototype is as follows:

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

HTTP test

The Net/http/httptest package provides tools for HTTP-related code, and we can create a temporary httptest in our test code. Server to test the code that sent the HTTP request:

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {    fmt.Fprintln(w, "Hello, client")}))defer ts.Close()res, err := http.Get(ts.URL)if err != nil {    log.Fatal(err)}greeting, err := ioutil.ReadAll(res.Body)res.Body.Close()if err != nil {    log.Fatal(err)}fmt.Printf("%s", greeting)

You can also create an answering logger, httptest. Responserecorder to detect the contents of an answer:

handler := func(w http.ResponseWriter, r *http.Request) {    http.Error(w, "something failed", http.StatusInternalServerError)}req, err := http.NewRequest("GET", "http://example.com/foo", nil)if err != nil {    log.Fatal(err)}w := httptest.NewRecorder()handler(w, req)fmt.Printf("%d - %s", w.Code, w.Body.String())

Test process Action Behavior

When we are measured by the function of the operation process, the program can be tested as a sub-process. Here is an example:

//被测试的进程退出函数func Crasher() {    fmt.Println("Going down in flames!")    os.Exit(1)}//测试进程退出函数的测试函数func TestCrasher(t *testing.T) {    if os.Getenv("BE_CRASHER") == "1" {        Crasher()        return    }    cmd := exec.Command(os.Args[0], "-test.run=TestCrasher")    cmd.Env = append(os.Environ(), "BE_CRASHER=1")    err := cmd.Run()    if e, ok := err.(*exec.ExitError); ok && !e.Success() {        return    }    t.Fatalf("process ran with err %v, want exit status 1", err)}

Resources

https://talks.golang.org/2014 ...
https://golang.org/pkg/testing/
Https://golang.org/pkg/testin ...
Https://golang.org/pkg/testin ...
Https://golang.org/pkg/net/ht ...

Original link: Http://tabalt.net/blog/golang ...

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.