Golang單元測試(go test )


前言

TDD(Test Driven Development),那么我們如何做到可反復、無遺漏、高效地測試我們自己寫的的代碼?實現敏捷開發呢?

這就需要我們自己給自己寫的代碼寫測試用例!

參考

 

本文主要介紹下在Go語言中如何做單元測試、基准測試、非功能測試。

 

go test介紹

想要測試Go代碼需要依賴go test命令,go test命令更像1個框架:

在包目錄內所有測試文件必須以_test.go結尾go build不會把這些測試文件編譯到最終的可執行文件中

 

 

ps:

我們執行go test命令時,它會遍歷該go包中所有以_test.go結尾的測試文件, 然后調用並執行測試文件中符合go test 規則的函數幫助我們實現自動化測試。

其過程為生成1個臨時的main包用於調用相應的測試函數,然后構建並運行測試文件中的函數、報告測試結果,最后清理測試中生成的臨時文件。

 

根據測試維度可以把包內以_test.go結尾的測試文件中的函數划分為以下3種:

類型 格式 作用
測試函數 函數名前綴為Test 測試程序的一些邏輯行為是否正確
基准函數 函數名前綴為Benchmark 測試函數的性能(執行時間、內存申請情況)
示例函數 函數名前綴為Example 為文檔提供示例文檔

 

單元測試函數(unit testing):是指對軟件中的最小可測試單元進行檢查和驗證。對於單元測試中單元的含義,一般來說,要根據實際情況去判定其具體含義,如C語言中單元指一個函數,Java里單元指一個類,圖形化的軟件中可以指一個窗口或一個菜單等。總的來說,單元就是人為規定的最小的被測功能模塊。

基准測試函數:測試程序執行時間復雜度、空間復雜度

示例函數:為調用該功能代碼的人提供演示

 

以上了解到為了可以使用golang內置的 go test命令實現自動化測試 需要做以下步驟:

1.在需要測試代碼的同一目錄下,准備一些以x_test.go結尾的測試文件

2.執行go test自動發現x_test.go結尾的測試文件並執行其內部符合go test 格式的函數

 

有go test這個測試工具之后,對於1個golang程序員來說,主要學習如何准備測試文件、測試文件中的測試函數需要遵循哪些格式規范,go test才會幫我去自動調用、執行我寫的測試用例!

 

單元測試函數

單元測試函數就是1個針對源碼中1個單元(func/class)進行功能測試的函數。

 

單元測試函數格式

1.每個函數必須導入testing包

import (
	"reflect"
	"testing"
)

 

2.測試函數格式

單元測試函數的名字必須以Test開頭,可選的后綴名必須以大寫字母開頭

每個單元測試函數的參數必須為*testing.T,參數t用於報告測試是否失敗以及日志信息。

func TestAdd(t *testing.T){ ... }
func TestSum(t *testing.T){ ... }
func TestLog(t *testing.T){ ... }

  

testing.T參數的擁有的方法如下:

func (c *T) Error(args ...interface{})
func (c *T) Errorf(format string, args ...interface{})
func (c *T) Fail()
func (c *T) FailNow()
func (c *T) Failed() bool
func (c *T) Fatal(args ...interface{})
func (c *T) Fatalf(format string, args ...interface{})
func (c *T) Log(args ...interface{})
func (c *T) Logf(format string, args ...interface{})
func (c *T) Name() string
func (t *T) Parallel()
func (t *T) Run(name string, f func(t *T)) bool
func (c *T) Skip(args ...interface{})
func (c *T) SkipNow()
func (c *T) Skipf(format string, args ...interface{})
func (c *T) Skipped() bool

 

3.驗證測試驅動開發理念

假設現在開發了1個單元(函數), 該單元的功能是對string類型的變量進行split。

split.go  (源代碼)

package splitString
import "strings"

//Newsplit 切割字符串
//example:
//abc,b=>[ac]
func Newsplit(str, sep string) (des []string) {
	index := strings.Index(str, sep)
	for index > -1 {
		sectionBefor := str[:index]
		des = append(des, sectionBefor)
		str = str[index+1:]
		index = strings.Index(str, sep)
	}
	//最后1
	des = append(des, str)
	return
}

 

split_test.go(單元測試代碼)

package splitString

import (
	"reflect"
	"testing"
)

//測試用例1:以字符分割
func TestSplit(t *testing.T) {
	got := Newsplit("123N456", "N")
	want := []string{"123", "456"}
	//DeepEqual比較底層數組
	if !reflect.DeepEqual(got, want) {
		//如果got和want不一致說明你寫得代碼有問題
		t.Errorf("The values of %v is not %v\n", got, want)
	}

}

//測試用例2:以標點符號分割
func TestPunctuationSplit(t *testing.T) {
	got := Newsplit("a:b:c", ":")
	want := []string{"a", "b", "c"}
	if !reflect.DeepEqual(got, want) {
		t.FailNow()//出錯就stop別往下測了!
	}

}

  

It's the truth that the test driven the developmen.

我在原來測試用例的基礎上增加了1個測試用例3

package splitString

import (
	"reflect"
	"testing"
)

//測試用例1:以字符分割
func TestSplit(t *testing.T) {
	got := Newsplit("123N456", "N")
	want := []string{"123", "456"}
	//DeepEqual比較底層數組
	if !reflect.DeepEqual(got, want) {
		//如果got和want不一致說明你寫得代碼有問題
		t.Errorf("The values of %v is not %v\n", got, want)
	}

}

//測試用例2:以標點符號分割
func TestPunctuationSplit(t *testing.T) {
	got := Newsplit("a:b:c", ":")
	want := []string{"a", "b", "c"}
	if !reflect.DeepEqual(got, want) {
		t.FailNow() //出錯就stop別往下測了!
	}

}

//測試用例3:增加分隔符的長度
func TestMultipleChartSplit(t *testing.T) {
	got := Newsplit("hellowbsdjshdworld", "bsdjshd")
	want := []string{"hellow", "world"}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("無法通過多字符分隔符的測試!got: %v want:%v\n", got, want) //出錯就stop別往下測了!
	}

}

  

執行go test測試出bug無法 使用多個字符分割字符串

D:\goproject\src\LearningTest\splitString>go test -v
=== RUN   TestSplit
--- PASS: TestSplit (0.00s)
=== RUN   TestPunctuationSplit
--- PASS: TestPunctuationSplit (0.00s)
=== RUN   TestMultipleChartSplit
--- FAIL: TestMultipleChartSplit (0.00s)
    split_test.go:35: 無法通過多字符分隔符的測試!got: [hellow sdjshdworld] want:[hellow world]
FAIL
exit status 1
FAIL    LearningTest/splitString        0.037s

 

驅動我繼續開發源碼

package splitString
import "strings"

//Newsplit 切割字符串
//example:
//abc,b=>[ac]
func Newsplit(str, sep string) (des []string) {
	index := strings.Index(str, sep)
	for index > -1 {
		sectionBefor := str[:index]
		des = append(des, sectionBefor)
		str = str[index+len(sep):]
		index = strings.Index(str, sep)
	}
	//最后1
	des = append(des, str)
	return
}

  

 

測試組

以上的測試模式中我們每寫個測試用例就需要再寫1個函數,可以繼續優化測試代碼!

利用結構體組織測試數據把多個測試用例合到一起,在1個函數內對1組測試用例進行統一測試。

 

測試代碼

package splitString

import (
	"reflect"
	"testing"
)

//測試組:在1個函數中寫多個測試用例,切支持靈活擴展!

type testCase struct {
	str      string
	separate string
	want     []string
}

var testGroup = []testCase{
	//測試用例1:單個英文字母
	testCase{
		str:      "123N456",
		separate: "N",
		want:     []string{"123", "456"},
	},
	//測試用例2:符號
	testCase{
		str:      "a:b:c",
		separate: ":",
		want:     []string{"a", "b", "c"},
	},
	//測試用例3:多個英文字母
	testCase{
		str:      "hellowbsdjshdworld",
		separate: "bsdjshd",
		want:     []string{"hellow", "world"},
	},
	//測試用例4:單個漢字
	testCase{
		str:      "山西運煤車煤運西山",
		separate: "山",
		want:     []string{"西運煤車煤運西"},
	},

	//測試用例4:多個漢字
	testCase{
		str:      "京北北京之北",
		separate: "北京",
		want:     []string{"京北", "之北"},
	},
}

func TestSplit(t *testing.T) {
	for _, test := range testGroup {
		got := Newsplit(test.str, test.separate)
		if !reflect.DeepEqual(got, test.want) {
			t.Fatalf("失敗!got:%#v want:%#v\n", got, test.want)
		}
	}

}

  

源碼

測試驅動開發!源碼又發現了新的bug!

package splitString

import "strings"

//Newsplit 切割字符串
//example:
//abc,b=>[ac]
func Newsplit(str, sep string) (des []string) {
	index := strings.Index(str, sep)
	for index > -1 {
		sectionBefor := str[:index]
		if len(sectionBefor) >= 1 {
			des = append(des, sectionBefor)
		}
		str = str[index+len(sep):]
		index = strings.Index(str, sep)
	}
	//最后1
	if len(str) >= 1 {
		des = append(des, str)
	}

	return
}

  

測試結果

D:\goproject\src\LearningTest\splitString>go test -v
=== RUN   TestSplit
--- PASS: TestSplit (0.00s)
PASS
ok      LearningTest/splitString        0.022s

D:\goproject\src\LearningTest\splitString>

  

子測試

基於測試組對測試代碼再次進行優化,利用使用t *testing.T參數的run方法去執行測試用例

這種方法可以針對測試組里的1個測試用例進行單獨測試,所以也叫子測試。

測試代碼

package splitString

import (
	"reflect"
	"testing"
)

//子測試
type testCase struct {
	str      string
	separate string
	want     []string
}

var testGroup = map[string]testCase{
	"punctuation": testCase{
		str:      "a:b:c",
		separate: ":",
		want:     []string{"a", "b", "c"},
	},
	"sigalLetter": testCase{
		str:      "123N456",
		separate: "N",
		want:     []string{"123", "456"},
	},

	"MultipleLetter": testCase{
		str:      "hellowbsdjshdworld",
		separate: "bsdjshd",
		want:     []string{"hellow", "world"},
	},
	"singalRune": testCase{
		str:      "山西運煤車煤運西山",
		separate: "山",
		want:     []string{"西運煤車煤運西"},
	},
	"multiplRune": testCase{
		str:      "京北北京之北",
		separate: "北京",
		want:     []string{"京北", "之北"},
	},
}

//測試用例函數
func TestSplit(t *testing.T) {
	for name, test := range testGroup {
		//使用t參數的run方法
		t.Run(name, func(t *testing.T) {
			got := Newsplit(test.str, test.separate)
			if !reflect.DeepEqual(got, test.want) {
				t.Fatalf("失敗!got:%#v want:%#v\n", got, test.want)
			}
		})
	}
}

  

測試結果

D:\goproject\src\LearningTest\splitString>go test -v
=== RUN   TestSplit
=== RUN   TestSplit/punctuation
=== RUN   TestSplit/sigalLetter
=== RUN   TestSplit/MultipleLetter
=== RUN   TestSplit/singalRune
=== RUN   TestSplit/multiplRune
--- PASS: TestSplit (0.00s)
    --- PASS: TestSplit/punctuation (0.00s)
    --- PASS: TestSplit/sigalLetter (0.00s)
    --- PASS: TestSplit/MultipleLetter (0.00s)
    --- PASS: TestSplit/singalRune (0.00s)
    --- PASS: TestSplit/multiplRune (0.00s)
PASS
ok      LearningTest/splitString        0.037s

針對某1個測試用例進行單獨測試 D:\goproject\src\LearningTest\splitString>go test -run=TestSplit/punctuation PASS ok LearningTest/splitString 0.042s D:\goproject\src\LearningTest\splitString>

 

測試覆蓋率

測試覆蓋率是你的代碼被測試套件覆蓋的百分比。

通常我們使用的都是語句的覆蓋率,也就是在測試中至少被運行一次的代碼占總代碼的比例。

Go提供內置功能來檢查你的代碼覆蓋率。我們可以使用

go test -cover

查看測試覆蓋率。

D:\goproject\src\LearningTest\splitString> go test -cover
PASS
coverage: 100.0% of statements
ok      LearningTest/splitString        0.042s

D:\goproject\src\LearningTest\splitString>

 

go test -cover -coverprofile=測試報告文件

把測試覆蓋率的詳細信息輸出到文件

D:\goproject\src\LearningTest\splitString>go test -cover -coverprofile=test_report.out
PASS
coverage: 100.0% of statements
ok      LearningTest/splitString        0.040s

  

go tool cover -html=測試報告文件

把測試報告輸出到文件,就是為了分析測試結果,go內置的工具支持以HTML的方式打開測試報告文件!

D:\goproject\src\LearningTest\splitString>go tool cover -html=test_report.out

 

上圖中每個用綠色標記的語句塊表示被覆蓋了,而紅色的表示沒有被覆蓋。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM