Go 語言單元測試實踐

原文連接:Go 語言單元測試實踐html

什麼是軟件測試git

軟件測試是一個過程,該過程對軟件(計算機程序)進行各類操做來發現軟件錯誤。github

爲何要進行軟件測試?golang

進行軟件測試能夠幫助咱們驗證軟件的各類功能正常,保證軟件的正常工做從而提升軟件質量。而且在實踐中已被證實是很有成效的編程

測試驅動開發的由來:瀏覽器

一個從大量實踐中得出的結論:人們發如今軟件開發週期中,軟件錯誤每進入到下一個階段要修正它所付出的時間和人力會出人意表的翻上十倍。因此更早地進行軟件測試能夠更早地發現軟件錯誤,從而大大減小後期修正的成本。後來又有人提出了測試驅動開發(TDD: Test-driven development),主體思想就是先編寫測試程序,再實現程序功能框架

下面就來介紹如何在 Go 語言中進行軟件測試中較爲重要的一環:單元測試。函數

Go 語言單元測試實踐
Go 語言單元測試實踐

單元測試簡介

單元測試就是針對程序最小單元的測試post

最小單元在過程化編程中指的是函數;在面向對象編程中指的是方法單元測試

Go 語言對軟件測試的支持

  • testing 包:標準庫中一個簡單、強大的測試框架。
  • go test 命令:go tool 中一個按照約定和組織的測試代碼的命令。

規定(必須遵循的條例):

  • 只有以 _test.go 結尾的文件纔會被視做是測試文件。如:swap.go
  • 測試函數命名必須以 Test 開頭,然後緊接着的第一個字母必須大寫。如:TestSwap
  • 測試函數必須傳入一個指向 testing.Ttesting.B(基準測試)的指針做爲參數。如:func TestSwap(t *testing.T),且沒有返回值。

建議(最佳方法實踐條例):

  • 將測試文件和要測試的代碼放在同一個包(Go 語言組織也建議包名和文件所在目錄名相同)。也就是說將這兩個文件的 package xxx 寫成相同的。
  • 將測試文件命名爲 要測試的代碼文件名_test.go。如:util.go 的測試文件名應爲 util_test.go
  • 將測試函數命名爲 Test要測試的函數名稱。如:Swap 函數的測試函數命名應爲 TestSwap,這個函數應只包含測試 Swap 函數的內容。

Go 語言單元測試實踐

示例函數說明

這裏有一個用 Go 語言編寫的 Swap 函數(交換切片中的兩個值):

  1. 文件名:swap.go
  2. 包名:swap
  3. 測試文件名:swap_test.go
  4. 測試文件包名:swap

這兩個文件都放在 $GOPATH/src/swap/ 目錄下。

  • 測試整個包: go test swapswap 爲基於 $GOPATH/src/ 的相對目錄。
  • 測試單個測試函數: go test swap -run TestSwapswap 一樣爲相對目錄,TestSwap 爲測試函數名。

下面會展現如何爲這個 Swap 函數編寫單元測試、將單元測試改寫成表驅動測試的形式並顯示代碼的測試覆蓋率

示例函數代碼

package swap

import (
	"errors"
)

// Swap exchanges s[i] and s[j].
func Swap(s []interface{}, i, j int) error {
	if s == nil {
		return errors.New("slice can't be nil")
	}
	if (i < 0 || i >= len(s)) || (j < 0 || j >= len(s)) {
		return errors.New("illegal index")
	}

	s[i], s[j] = s[j], s[i]

	return nil
}

// IsSameSlice determines two slice is it the same.
func IsSameSlice(a, b []interface{}) bool {
	if len(a) != len(b) {
		return false
	}
	if (a == nil) != (b == nil) {
		return false
	}

	for i, v := range a {
		if b[i] != v {
			return false
		}
	}

	return true
}
複製代碼

單元測試示例

package swap

import (
	"testing"
)

func TestSwap(t *testing.T) {
	input1 := []interface{}{1, 2}
	i1 := 0
	j1 := 1
	want1 := []interface{}{2, 1}
	if err := Swap(input1, i1, j1); err != nil {
		t.Error(err)
	}

	if !IsSameSlice(input1, want1) {
		t.Errorf("got %v, want %v", input1, want1)
	}

	input2 := []interface{}{1, 'a', "aa"}
	i2 := 0
	j2 := 2
	want2 := []interface{}{"aa", 'a', 1}
	if err := Swap(input2, i2, j2); err != nil {
		t.Error(err)
	}

	if !IsSameSlice(input2, want2) {
		t.Errorf("got %v, want %v", input2, want2)
	}
}
複製代碼

這段代碼就是爲 Swap 編寫的簡單單元測試,能夠看出,若是測試數據變多,代碼就會有不少冗餘。這種問題的一個有效地解決方法就是用表驅動測試來實現單元測試

表驅動測試示例

表驅動測試是單元測試的一種形式,經過把測試條件都寫在一張表裏,就能夠動態地添加測試數據而不用改動太多代碼。

package swap

import (
	"testing"
)

func TestSwap(t *testing.T) {
	tests := []struct {
		input []interface{}
		i     int
		j     int
		want  []interface{}
	}{
		{[]interface{}{1, 2}, 0, 1, []interface{}{2, 1}},
		{[]interface{}{1, 'a', "aa"}, 0, 2, []interface{}{"aa", 'a', 1}},
	}
	for i, tt := range tests {
		if err := Swap(tt.input, tt.i, tt.j); err != nil {
			t.Error(err)
		}

		if !IsSameSlice(tt.input, tt.want) {
			t.Errorf("%v. got %v, want %v", i, tt.input, tt.want)
		}
	}
}
複製代碼

查看測試覆蓋率

測試覆蓋率就是測試運行到的被測試代碼的代碼數目。其中以語句的覆蓋率最爲簡單和普遍,語句的覆蓋率指的是在測試中至少被運行一次的代碼佔總代碼數的比例

使用 go tool cover 命令來查看有關測試覆蓋率命令行的幫助。

注意:首先必須保證測試是可以經過的

  • 測試整個包: go test -cover=true swap
  • 測試單個測試函數: go test -cover=true swap -run TestSwap

-cover=true 選項會開啓覆蓋率說明

生成 HTML 報告

前面展現的都是命令行下的報告,不夠直觀。

其實 go tool cover 支持更友好的輸出,如:HTML 報告。

go test -cover=true swap -coverprofile=out.out 將在當前目錄生成覆蓋率數據。

配合 go tool cover -html=out.out 在瀏覽器中打開 HTML 報告。

或者使用 go tool cover -html=out.out -o=out.html 生成 HTML 文件。

Go 語言測試覆蓋率 HTML報告
Go 語言測試覆蓋率 HTML 報告

第三方測試框架

通常狀況下,Go 語言標準庫對測試的支持已足夠強大。可是也有許多第三方的庫提供了各類各樣的功能。這裏只介紹兩個使用普遍的庫和其主要功能。

使用 testify 包簡化測試代碼

安裝:go get -u github.com/stretchr/testify

testify 提供 assertmockhttp 三個包進行更多樣的測試。下面用其中的 assert 包改寫前面的例子。

package swap

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestSwap(t *testing.T) {
	input1 := []interface{}{1, 2}
	i1 := 0
	j1 := 1
	want1 := []interface{}{2, 1}
	if assert.Nil(t, Swap(input1, i1, j1)) {
		t.FailNow()
	}

	assert.Equal(t, input1, want1)

	input2 := []interface{}{1, 'a', "aa"}
	i2 := 0
	j2 := 2
	want2 := []interface{}{"aa", 'a', 1}
	if assert.Nil(t, Swap(input2, i2, j2)) {
		t.FailNow()
	}

	assert.Equal(t, input2, want2)
}
複製代碼

使用 gotests 生成測試代碼

安裝:go get -u github.com/cweill/gotests

gotests 能夠爲程序生成表驅動測試,在生成以後再添加測試數據便可完成測試代碼的編寫。

使用命令:gotests -all -w go/src/swap/swap.go 便可生成關於 swap.go 文件中全部函數的表驅動測試形式的單元測試,包含在與 swap.go 同目錄下的 swap_test.go 文件中。

package swap

import "testing"

func TestSwap(t *testing.T) {
	type args struct {
		s []interface{}
		i int
		j int
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
	}{
		// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := Swap(tt.args.s, tt.args.i, tt.args.j); (err != nil) != tt.wantErr {
				t.Errorf("Swap() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}
複製代碼

上面就是運行命令以後生成的代碼。在 TODO 中添加測試數據便可。

package swap

import "testing"

func TestSwap(t *testing.T) {
	type args struct {
		s []interface{}
		i int
		j int
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
	}{
		{"1", args{[]interface{}{1, 2}, 0, 1}, false},
		{"2", args{[]interface{}{1, 'a', "aa"}, 0, 2}, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := Swap(tt.args.s, tt.args.i, tt.args.j); (err != nil) != tt.wantErr {
				t.Errorf("Swap() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}
複製代碼

能夠看出這裏並無測試交換後的值是否正確,說明 gotests 的代碼會根據返回值生成,也說明了 gotests 對某些函數來講是不適用的或者說是須要手動修改的。

參考連接

Go 單元測試

Go 語言實戰筆記(二十一)| Go 單元測試

相關文章
相關標籤/搜索