GO TESTING;HOW,WHAT,WHY

Introduction

關於 Go 測試,咱們應該知道測試方式(或者說測試手段)、測試對象及測試緣由。html

How 測試方式

測試實現

舉個例子。針對字符串分割函數(以下),實現單元測試。git

package goTest

import "strings"

// Split slices s into all substrings separated by sep and
// returns a slice of the substrings between those separators.
func Split(s, sep string) []string {
    var result []string
    i := strings.Index(s, sep)
    for i > -1 {
        result = append(result, s[:i])
        s = s[i+len(sep):]
        i = strings.Index(s, sep)
    }
    return append(result, s)
}

在當前目錄下且同樣的包名 goTest ,寫一個簡單的 go 測試函數,以下:github

package goTest

import (
    "reflect"
    "testing"
)

func TestSplit(t *testing.T) {
    got := Split("a/b/c", "/")
    want := []string{"a", "b", "c"}
    if !reflect.DeepEqual(want, got) {
        t.Fatalf("expected: %v, got: %v", want, got)
    }
}

測試函數必須以 Test 開頭, 且必須攜帶一個 *testing.T 參數。 t *testing.T 提供改測試函數的打印、跳過、失敗功能。sql

測試執行

當前目錄下,執行 go test ,輸出以下:bash

> go test
PASS
ok      goTest  0.005s

若是項目中存在多個 package ,若要執行全部包的測試能夠在項目根目錄下使用 go test ./... ,輸出以下(例子:github.com/mattn/go-sqlite3):app

> go test ./...
ok      github.com/mattn/go-sqlite3    14.693s
?       github.com/mattn/go-sqlite3/upgrade    [no test files]

代碼測試覆蓋率

仍是以字符串分割函數爲例, 獲取當前代碼測試覆蓋率方式以下:函數

> go test -coverprofile=c.out
PASS
coverage: 100.0% of statements
ok      goTest    0.005s

數據顯示覆蓋率爲 100% 。若要以 HTML 方式顯示可使用命令 go tool cover -html=c.out單元測試

【tip】 一行命令 cover 獲取當前目錄下的代碼測試覆蓋度。 在 ~/.bashrc 中添加以下命令:測試

cover () {
    local t=$(mktemp -t cover)
    go test $COVERFLAGS -coverprofile=$t $@ \
      && go tool cover -func=$t \
      && unlink $t
}

執行後獲取的測試覆蓋度結果以下:優化

> cover
PASS
coverage: 100.0% of statements
ok      goTest    0.008s
goTest/wwg_split.go:7:    Split         100.0%
total:            (statements)     100.0%

問題:測試覆蓋率 100% ,結束了?

多個測試用例的狀況下,使用表組測試用例裝填。更改 TestSplit 以下:

func TestSplit(t *testing.T) {
    tests := []struct{
        input string
        sep string
        want []string
    }{
        {input: "a/b/c", sep: "/", want: []string{"a", "b", "c"}},
        {input: "a/b/c", sep: ",", want: []string{"a/b/c"}},
        {input: "a/b/c/", sep: "/", want: []string{"a", "b", "c"}},  // trailing sep
        {input: "abc", sep: "/", want: []string{"abc"}},
    }

    for _, tc := range tests {
        got := Split(tc.input, tc.sep)
        if !reflect.DeepEqual(tc.want, got) {
            t.Fatalf("expected: %v, got: %v", tc.want, got)
        }
    }
}

增長測試用例 trailing sep 後,執行測試,結果以下:

> go test
--- FAIL: TestSplit (0.00s)
    wwg_split_test.go:23: expected: [a b c], got: [a b c ]
FAIL
exit status 1
FAIL    goTest    0.005s

根據該結果很難一會兒在表組測試用例中查出是哪條。能夠將 表組測試用例實現改成 map 形式 ,具體以下:

func TestSplit(t *testing.T) {
    tests := map[string]struct{
        input string
        sep string
        want []string
    }{
        "simple": {input: "a/b/c", sep: "/", want: []string{"a", "b", "c"}},
        "wrong sep": {input: "a/b/c", sep: ",", want: []string{"a/b/c"}},
        "trailing sep": {input: "a/b/c/", sep: "/", want: []string{"a", "b", "c"}},
        "no sep": {input: "abc", sep: "/", want: []string{"abc"}},
    }

    for name, tc := range tests {
        got := Split(tc.input, tc.sep)
        if !reflect.DeepEqual(tc.want, got) {
            t.Errorf("%s expected: %v, got: %v", name, tc.want, got)
        }
    }
}

執行測試結果以下:

> go test
--- FAIL: TestSplit (0.00s)
    wwg_split_test.go:23: trailing sep expected: [a b c], got: [a b c ]
FAIL
exit status 1
FAIL    goTest    0.005s

Sub tests 使用,及 '%#v' format 使用,更改 TestSplit 以下:

func TestSplit(t *testing.T) {
    tests := map[string]struct{
        input string
        sep string
        want []string
    }{
        "simple": {input: "a/b/c", sep: "/", want: []string{"a", "b", "c"}},
        "wrong sep": {input: "a/b/c", sep: ",", want: []string{"a/b/c"}},
        "trailing sep": {input: "a/b/c/", sep: "/", want: []string{"a", "b", "c"}},
        "no sep": {input: "abc", sep: "/", want: []string{"abc"}},
    }

    for name, tc := range tests {
        t.Run(name, func(t *testing.T) {
            got := Split(tc.input, tc.sep)
            if !reflect.DeepEqual(tc.want, got) {
                t.Fatalf("%s expected: %#v, got: %#v", name, tc.want, got)
            }
        })
    }
}

測試結果以下:

> go test
--- FAIL: TestSplit (0.00s)
    --- FAIL: TestSplit/trailing_sep (0.00s)
        wwg_split_test.go:24: trailing sep expected: []string{"a", "b", "c"}, got: []string{"a", "b", "c", ""}
FAIL
exit status 1
FAIL    goTest    0.005s

更好的打印格式,能夠訪問:

使用 google/go-cmp 優化打印, 更改 TestSplit 以下:

for name, tc := range tests {
        t.Run(name, func(t *testing.T) {
            got := Split(tc.input, tc.sep)
            diff := cmp.Diff(tc.want, got)
            if diff != "" {
                t.Fatalf(diff)
            }
        })
    }

執行測試結果以下:

> go test
--- FAIL: TestSplit (0.00s)
    --- FAIL: TestSplit/trailing_sep (0.00s)
        wwg_split_test.go:29:   []string{
                  "a",
                  "b",
                  "c",
            +     "",
              }
FAIL
exit status 1
FAIL    goTest    0.005s

修復bug後 Split 代碼以下:

// Split slices s into all substrings separated by sep and
// returns a slice of the substrings between those separators.
func Split(s, sep string) []string {
    var result []string
    i := strings.Index(s, sep)
    for i > -1 {
        result = append(result, s[:i])
        s = s[i+len(sep):]
        i = strings.Index(s, sep)
    }
    **if len(s) > 0 {**
        result = append(result, s)
    }
    return result**
}

執行測試,結果以下:

> go test
PASS
ok      goTest    0.006s

> cover
PASS
coverage: 100.0% of statements
ok      goTest    0.006s
goTest/wwg_split.go:7:    Split        100.0%
total:                  (statements)    100.0%

What 測試對象

Q_1:Go 應該測試全部因子嗎?

A_1:顯然不是。

Q_2:什麼時候編寫測試? 1.編碼完成後? 2.編碼前? 3.其餘人遍寫測試,像QA、TE? 4.項目設計人員編寫測試?

A_2:編碼的同時編寫測試代碼(TDD)Article TheThreeRulesOfTdd

Q_3:C 單元測試對象是 function ,Java 單元測試對象是 Class ,類內部的方法, Go 的單元測試對象是?

A_3:package 。測試行爲,而非實施。 "The public API of a package declare this is what(行爲) I do, not this is how(實施) I do it."

Why 測試緣由

即便你不作代碼測試,別人也會作。本身發現 issues 總比別人發現來得好,不是嗎?

  1. 大部分的測試(自動化)應該是開發人員本身作。
  2. 手工測試不該該是你測試的主體部分,由於手工測試的複雜度爲O(n)
  3. 測試能夠確保您始終能夠運送主分支
  4. 測試肯定軟件行爲(作什麼、不作什麼)
  5. 測試讓你有信心修改他人的代碼

總結

  • You should write tests.
  • You should write tests at the same time as you write your code.
  • Each Go package is a self contained unit.
  • Your tests should assert the observable behaviour of your package, not its implementation.
  • You should design your packages around their behaviour, not their implementation.

【注】部分資料源於GopherChina 2019 - 'How to write testable code'

相關文章
相關標籤/搜索