單元測試是最小的測試單位,所以執行速度快且可靠,通常由開發人員自行撰寫
單元測試的高度涵蓋是自動化測試 (Test Automation) 的核心
單元測試關注的是測試程式本身邏輯,所以必須要把外部依賴(Database、File System IO)全部排除。常會使用Mock Data(假資料)來替代從外部依賴獲取資料的流程
整合測試比單元測試要高一級,是測試兩個以上的模組之間的交互作用符合預期,對模擬環境的完整度有較高要求
回歸測試是指重複執行既有的全部或部分的相同測試,需要根據需求、時程等問題選擇不同的執行策略
略
以下將使用幾個範例講解寫測試的方式和介紹一些套件的使用
使用Go自帶的testing
測試框架,配合go test
指令來實現測試
單元測試檔案必須為_test.go
結尾、測試 func 須符合 Test...
的規則
這樣下go test
指令時才會有效被執行
validator // 資料夾名稱
├── validator.go // 功能所在的檔案
└── validator_test.go // 撰寫針對該檔案的測試
此功能可用來驗證傳入的字串是否為合法的uuid
package validator
import "github.com/google/uuid"
func IsValidUUID(u string) bool {
_, err := uuid.Parse(u)
return err == nil
}
可以利用 gotests
工具自動產生單元測試:
游標停在欲測試的func上 -> 鍵盤按ctl+shift+P
彈出選單 -> 點選「Go:Generate Unit Tests For Function」
將會根據該func自動產生測試的檔案以及基礎的程式碼,再自行補上要測試的case後即可使用
package validator
import "testing"
func TestIsValidUUID(t *testing.T) {
type args struct {
u string
}
tests := []struct {
name string
args args
want bool
}{
// test cases
{
"caseReturnTrue",
args{
u: "a0a8aea5-cc40-4293-b1f3-c3bc4e53d941",
},
true,
},
{
"caseReturnFalse",
args{
u: "a0a8aea5-cc40-4293-b1f3-",
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsValidUUID(tt.args.u); got != tt.want {
t.Errorf("IsValidUUID() = %v, want %v", got, tt.want)
}
})
}
}
呼叫 testing.T 的 Error, Errorf, FailNow, Fatal, FatalIf 方法,說明測試不透過
呼叫 Log 方法用來記錄測試的資訊
當然,也可以自行撰寫測試:
Test Func 建議使用有意義的命名,且須符合Test{Xxx...}
的規則
package validator
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsValidUUID_ReturnTrue(t *testing.T) {
rlt := IsValidUUID("a0a8aea5-cc40-4293-b1f3-c3bc4e53d941")
assert.True(t, rlt)
}
func TestIsValidUUID_ReturnFalse(t *testing.T) {
rlt := IsValidUUID("a0a8aea5-cc40-4293-b1f3-c3bc4-")
assert.False(t, rlt)
}
testify
assert
package
go test -v {filePath},沒有加的話就是找當前路徑下的_test
檔案
預設會執行檔案內的所有測試案例
$ go test -v ./validator
=== RUN TestIsValidUUID_ReturnTrue
--- PASS: TestIsValidUUID_ReturnTrue (0.00s)
=== RUN TestIsValidUUID_ReturnFalse
--- PASS: TestIsValidUUID_ReturnFalse (0.00s)
PASS
ok go_traning/unit_test/validator 0.358s
由於指令中加了-v
,會顯示測試的詳細過程(不然只會有PASS、ok的那兩行)
go test -v -run {testFuncName} {filePath},其中 {testFuncName} 是為正則表達式
$ go test -v -run TestIsValidUUID_ReturnTrue ./validator
=== RUN TestIsValidUUID_ReturnTrue
validator_test.go:45:
Error Trace: /Users/esther_lin/Desktop/Go traning/goUnitTest/validator/validator_test.go:45
Error: Should be true
Test: TestIsValidUUID_ReturnTrue
--- FAIL: TestIsValidUUID_ReturnTrue (0.00s)
FAIL
exit status 1
FAIL go_traning/unit_test/validator 0.353s
因為是正則表達的關係,如果是執行以下指令:
$ go test -v -run TestIsValidUUID_Return ./validator
…ReturnTrue 以及 …ReturnFalse 兩個func都會被執行
若要完全指定,使用 {testFuncName$} 即可只執行符合該名稱的測試
$ go test -cover
PASS
coverage: 100.0% of statements
ok go_traning/unit_test/mock/db 0.336s
ex.go
package testfunc
import (
"package_name/configs" // 引用的package如果裡面有init,在測試時會被執行
"fmt"
)
// 自己的init則不會在測試中被執行到
func init() {
fmt.Println(configs.AppConfig.App.Env)
}
func GetName() string {
return "Hello"
}
ex_test.go
func TestGetName(t *testing.T) {
tests := []struct {
name string
want string
}{
{
"1",
"Hello",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetName(); got != tt.want {
t.Errorf("GetName() = %v, want %v", got, tt.want)
}
})
}
}
configs/app.go
func init(){
loadAppConfig()
}
func loadAppConfig(){
AppConfig.App.Env = os.Getenv("Env")
if len(AppConfig.App.Env) == 0{
panic("無效的環境參數")
}
}
執行時,會有錯誤
$ go test -timeout 30s -run ^TestGetName$ crown_connector/testfunc
panic: 無效的環境參數
goroutine 1 [running]:
crown_connector/configs.loadAppConfig()
/file_path/configs/app.go:69 +0x2a9
crown_connector/configs.init.0()
/file_path/configs/app.go:53 +0x17
FAIL crown_connector/testfunc 0.359s
FAIL
是因為呼叫到了/configs
裡的 init()
,要避免這個情況,必須把init改為手動呼叫func初始化
做出一個模擬的(假的) Function 或 Object 來取代原本程式邏輯內部相對應的 Function 或 Object,如DB回傳、外部API回傳…等
Go官方提供的套件,可以直接針對檔案產出對應的 mock 檔
但要注意,mock 是針對 interface 產生,檔案中必須要有定義 interface 否則無法使用(對寫法有限制)
get_name.go
package db
type DB interface {
GetNameByIndex(index int) string
}
func GetName(db DB, index int) string {
return db.GetNameByIndex(index)
}
如果沒有用過 mockgen
的話,要先安裝:
$ go install github.com/golang/mock/mockgen@v1.6.0
要是套件安裝失敗,可以試試這兩個指令
$ export GOPATH=$HOME/go
$ export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
接著使用指令來產生 mock 檔案
$ mockgen -destination get_name_mock.go -package db -source get_name.go
mock_
前綴 source 的 package 名稱生成的內容會像這樣:
get_name_mock.go
// Code generated by MockGen. DO NOT EDIT.
// Source: get_name.go
// Package db is a generated GoMock package.
package db
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockDB is a mock of DB interface.
type MockDB struct {
ctrl *gomock.Controller
recorder *MockDBMockRecorder
}
// MockDBMockRecorder is the mock recorder for MockDB.
type MockDBMockRecorder struct {
mock *MockDB
}
// NewMockDB creates a new mock instance.
func NewMockDB(ctrl *gomock.Controller) *MockDB {
mock := &MockDB{ctrl: ctrl}
mock.recorder = &MockDBMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockDB) EXPECT() *MockDBMockRecorder {
return m.recorder
}
// GetNameByIndex mocks base method.
func (m *MockDB) GetNameByIndex(index int) string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetNameByIndex", index)
ret0, _ := ret[0].(string)
return ret0
}
// GetNameByIndex indicates an expected call of GetNameByIndex.
func (mr *MockDBMockRecorder) GetNameByIndex(index interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNameByIndex", reflect.TypeOf((*MockDB)(nil).GetNameByIndex), index)
}
建立 Test func 後,宣告一個 gomock controller
func TestGetName(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish() // 最後要關掉它
}
接著宣告 MockDB 的物件(以下命名為 m),將 ctrl 帶入
再根據 GetNameByIndex 方法去設定 (多個) 帶入的參數以及回傳的數值
EXPECT()
代表期望 GetNameByIndex 傳入相當於 Eq()
內的參數時
Return
中指定的值DoAndReturn
執行指定的行為後回傳// _mock.go 產生時定義好的func
// func名稱固定為 NewMock{interfaceName},New出來的物件型態固定為 Mock{interfaceName}
m := NewMockDB(ctrl)
// 設定只要帶入 index = 1 就睡一秒後再回傳 superman
m.
EXPECT().
GetNameByIndex(gomock.Eq(1)). // 由 _mock.go 所實作
DoAndReturn(func(_ int) string { // 此 func 型態必須符合 GetNameByIndex
time.Sleep(1000)
return "superman"
})
// 設定只要帶入 index = 2 就回傳 spiderman 這個字串
m.
EXPECT().
GetNameByIndex(gomock.Eq(2)).
Return("spiderman")
gomock 會假定這個 mock 至少會被調用一次,若無,測試時會報錯「missing call(s)」
欲避免此情況,可以在設定後方加上 .AnyTimes()
相關指令可以參考 Go Mock -gomock- 簡明教程
設定完 mock 後,要把 MockDB 物件帶入要測試的那個 func 裡
因為它有 implement interface 中的 Method (GetNameByIndex),所以能順利取代原本真正要去存取DB的 GetNameByIndex (觀念參考)
Convey(testCase.testName, t, func() {
name := GetName(m, testCase.arg) // call 要測試的那個 function
So(name, ShouldEqual, testCase.want)
})
goconvey package
完整的測試程式碼像這樣:
get_name_test.go
package db
import (
"testing"
"time"
"github.com/golang/mock/gomock"
. "github.com/smartystreets/goconvey/convey"
)
func TestGetName(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := NewMockDB(ctrl)
m.
EXPECT().
GetNameByIndex(gomock.Eq(1)).
DoAndReturn(func(_ int) string {
time.Sleep(1000)
return "superman"
})
m.
EXPECT().
GetNameByIndex(gomock.Eq(2)).
Return("spiderman")
testCases := []struct {
testName string
want string
arg int
}{
{
"超人",
"superman",
1,
},
{
"蜘蛛人",
"spiderman",
2,
},
}
for _, testCase := range testCases {
Convey(testCase.testName, t, func() {
name := GetName(m, testCase.arg)
So(name, ShouldEqual, testCase.want)
})
}
}
執行結果:
$ go test -v
=== RUN TestGetName
超人 ✔
1 total assertion
蜘蛛人 ✔
2 total assertions
--- PASS: TestGetName (0.00s)
PASS
ok go_traning/unit_test/mock/db 0.302s
Comma-delimited list of paths to unit test report files. Paths may be absolute or relative to the project root.
sonar.go.tests.reportPaths=report.json
sonar.go.coverage.reportPaths=coverage.out
$ go test -v ./... -coverprofile=coverage.out
coverage.out
mode: set
go_traning/unit_test/db/get_name.go:15.51,18.2 1 0
go_traning/unit_test/db/get_name.go:20.39,22.2 1 1
go_traning/unit_test/db/get_name_mock.go:25.49,29.2 3 1
go_traning/unit_test/db/get_name_mock.go:32.47,34.2 1 1
go_traning/unit_test/db/get_name_mock.go:37.51,42.2 4 1
go_traning/unit_test/db/get_name_mock.go:45.78,48.2 2 1
go_traning/unit_test/validator/validator.go:5.33,8.2 2 1
$ gocov convert cover.out
$ gocov-xml > report.xml
report.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
<coverage line-rate="0.9285714" branch-rate="0" lines-covered="13" lines-valid="14" branches-covered="0" branches-valid="0" complexity="0" version="" timestamp="1676262029871">
<packages>
<package name="go_traning/unit_test/db" line-rate="0.9166667" branch-rate="0" complexity="0" line-count="12" line-hits="11">
<classes>
<class name="-" filename="db/get_name.go" line-rate="1" branch-rate="0" complexity="0" line-count="1" line-hits="1">
<methods>
<method name="GetName" signature="" line-rate="1" branch-rate="0" complexity="0" line-count="0" line-hits="0">
<lines>
</lines>
</method>
</methods>
<lines>
</lines>
</class>
<class name="UserDB" filename="db/get_name.go" line-rate="0" branch-rate="0" complexity="0" line-count="1" line-hits="0">
<methods>
<method name="GetNameByIndex" signature="" line-rate="0" branch-rate="0" complexity="0" line-count="0" line-hits="0">
<lines>
</lines>
</method>
</methods>
<lines>
</lines>
</class>
<class name="-" filename="db/get_name_mock.go" line-rate="1" branch-rate="0" complexity="0" line-count="3" line-hits="3">
<methods>
<method name="NewMockDB" signature="" line-rate="1" branch-rate="0" complexity="0" line-count="0" line-hits="0">
<lines>
</lines>
</method>
</methods>
<lines>
</lines>
</class>
<class name="MockDB" filename="db/get_name_mock.go" line-rate="1" branch-rate="0" complexity="0" line-count="5" line-hits="5">
<methods>
<method name="EXPECT" signature="" line-rate="1" branch-rate="0" complexity="0" line-count="0" line-hits="0">
<lines>
</lines>
</method>
<method name="GetNameByIndex" signature="" line-rate="1" branch-rate="0" complexity="0" line-count="0" line-hits="0">
<lines>
</lines>
</method>
</methods>
<lines>
</lines>
</class>
<class name="MockDBMockRecorder" filename="db/get_name_mock.go" line-rate="1" branch-rate="0" complexity="0" line-count="2" line-hits="2">
<methods>
<method name="GetNameByIndex" signature="" line-rate="1" branch-rate="0" complexity="0" line-count="0" line-hits="0">
<lines>
</lines>
</method>
</methods>
<lines>
</lines>
</class>
</classes>
</package>
<package name="go_traning/unit_test/validator" line-rate="1" branch-rate="0" complexity="0" line-count="2" line-hits="2">
<classes>
<class name="-" filename="validator/validator.go" line-rate="1" branch-rate="0" complexity="0" line-count="2" line-hits="2">
<methods>
<method name="IsValidUUID" signature="" line-rate="1" branch-rate="0" complexity="0" line-count="0" line-hits="0">
<lines>
</lines>
</method>
</methods>
<lines>
</lines>
</class>
</classes>
</package>
</packages>
<sources>
<source>/Users/esther_lin/Desktop/Go traning/goUnitTest</source>
</sources>
</coverage>
$ go test "./..." -coverprofile="coverage.out" -covermode=count -json > report.json
report.json
{"Time":"2023-02-13T14:27:15.686684+08:00","Action":"output","Package":"go_traning/unit_test","Output":"? \tgo_traning/unit_test\t[no test files]\n"}
{"Time":"2023-02-13T14:27:15.686865+08:00","Action":"skip","Package":"go_traning/unit_test","Elapsed":0}
{"Time":"2023-02-13T14:27:16.484719+08:00","Action":"run","Package":"go_traning/unit_test/db","Test":"TestGetName"}
{"Time":"2023-02-13T14:27:16.484966+08:00","Action":"output","Package":"go_traning/unit_test/db","Test":"TestGetName","Output":"=== RUN TestGetName\n"}
{"Time":"2023-02-13T14:27:16.48499+08:00","Action":"output","Package":"go_traning/unit_test/db","Test":"TestGetName","Output":"\n"}
{"Time":"2023-02-13T14:27:16.484999+08:00","Action":"output","Package":"go_traning/unit_test/db","Test":"TestGetName","Output":" 超人 \u001b[32m✔\u001b[0m\n"}
{"Time":"2023-02-13T14:27:16.485009+08:00","Action":"output","Package":"go_traning/unit_test/db","Test":"TestGetName","Output":"\n"}
{"Time":"2023-02-13T14:27:16.485016+08:00","Action":"output","Package":"go_traning/unit_test/db","Test":"TestGetName","Output":"\u001b[31m\u001b[0m\u001b[33m\u001b[0m\u001b[32m\n"}
{"Time":"2023-02-13T14:27:16.485029+08:00","Action":"output","Package":"go_traning/unit_test/db","Test":"TestGetName","Output":"1 total assertion\u001b[0m\n"}
{"Time":"2023-02-13T14:27:16.485049+08:00","Action":"output","Package":"go_traning/unit_test/db","Test":"TestGetName","Output":"\n"}
{"Time":"2023-02-13T14:27:16.485056+08:00","Action":"output","Package":"go_traning/unit_test/db","Test":"TestGetName","Output":"\n"}
{"Time":"2023-02-13T14:27:16.485063+08:00","Action":"output","Package":"go_traning/unit_test/db","Test":"TestGetName","Output":" 蜘蛛人 \u001b[32m✔\u001b[0m\n"}
{"Time":"2023-02-13T14:27:16.48507+08:00","Action":"output","Package":"go_traning/unit_test/db","Test":"TestGetName","Output":"\n"}
{"Time":"2023-02-13T14:27:16.485078+08:00","Action":"output","Package":"go_traning/unit_test/db","Test":"TestGetName","Output":"\u001b[31m\u001b[0m\u001b[33m\u001b[0m\u001b[32m\n"}
{"Time":"2023-02-13T14:27:16.48509+08:00","Action":"output","Package":"go_traning/unit_test/db","Test":"TestGetName","Output":"2 total assertions\u001b[0m\n"}
{"Time":"2023-02-13T14:27:16.485098+08:00","Action":"output","Package":"go_traning/unit_test/db","Test":"TestGetName","Output":"\n"}
{"Time":"2023-02-13T14:27:16.48515+08:00","Action":"output","Package":"go_traning/unit_test/db","Test":"TestGetName","Output":"--- PASS: TestGetName (0.00s)\n"}
{"Time":"2023-02-13T14:27:16.485162+08:00","Action":"pass","Package":"go_traning/unit_test/db","Test":"TestGetName","Elapsed":0}
{"Time":"2023-02-13T14:27:16.485172+08:00","Action":"output","Package":"go_traning/unit_test/db","Output":"PASS\n"}
{"Time":"2023-02-13T14:27:16.485224+08:00","Action":"output","Package":"go_traning/unit_test/db","Output":"coverage: 91.7% of statements\n"}
{"Time":"2023-02-13T14:27:16.486132+08:00","Action":"output","Package":"go_traning/unit_test/db","Output":"ok \tgo_traning/unit_test/db\t0.317s\tcoverage: 91.7% of statements\n"}
{"Time":"2023-02-13T14:27:16.486194+08:00","Action":"pass","Package":"go_traning/unit_test/db","Elapsed":0.317}
{"Time":"2023-02-13T14:27:16.730648+08:00","Action":"run","Package":"go_traning/unit_test/validator","Test":"TestIsValidUUID_ReturnTrue"}
{"Time":"2023-02-13T14:27:16.730777+08:00","Action":"output","Package":"go_traning/unit_test/validator","Test":"TestIsValidUUID_ReturnTrue","Output":"=== RUN TestIsValidUUID_ReturnTrue\n"}
{"Time":"2023-02-13T14:27:16.730803+08:00","Action":"output","Package":"go_traning/unit_test/validator","Test":"TestIsValidUUID_ReturnTrue","Output":" validator_test.go:45: \n"}
{"Time":"2023-02-13T14:27:16.730818+08:00","Action":"output","Package":"go_traning/unit_test/validator","Test":"TestIsValidUUID_ReturnTrue","Output":" \tError Trace:\t/Users/esther_lin/Desktop/Go traning/goUnitTest/validator/validator_test.go:45\n"}
{"Time":"2023-02-13T14:27:16.730837+08:00","Action":"output","Package":"go_traning/unit_test/validator","Test":"TestIsValidUUID_ReturnTrue","Output":" \tError: \tShould be true\n"}
{"Time":"2023-02-13T14:27:16.73089+08:00","Action":"output","Package":"go_traning/unit_test/validator","Test":"TestIsValidUUID_ReturnTrue","Output":" \tTest: \tTestIsValidUUID_ReturnTrue\n"}
{"Time":"2023-02-13T14:27:16.730913+08:00","Action":"output","Package":"go_traning/unit_test/validator","Test":"TestIsValidUUID_ReturnTrue","Output":"--- FAIL: TestIsValidUUID_ReturnTrue (0.00s)\n"}
{"Time":"2023-02-13T14:27:16.730923+08:00","Action":"fail","Package":"go_traning/unit_test/validator","Test":"TestIsValidUUID_ReturnTrue","Elapsed":0}
{"Time":"2023-02-13T14:27:16.730938+08:00","Action":"run","Package":"go_traning/unit_test/validator","Test":"TestIsValidUUID_ReturnFalse"}
{"Time":"2023-02-13T14:27:16.730949+08:00","Action":"output","Package":"go_traning/unit_test/validator","Test":"TestIsValidUUID_ReturnFalse","Output":"=== RUN TestIsValidUUID_ReturnFalse\n"}
{"Time":"2023-02-13T14:27:16.730961+08:00","Action":"output","Package":"go_traning/unit_test/validator","Test":"TestIsValidUUID_ReturnFalse","Output":"--- PASS: TestIsValidUUID_ReturnFalse (0.00s)\n"}
{"Time":"2023-02-13T14:27:16.730972+08:00","Action":"pass","Package":"go_traning/unit_test/validator","Test":"TestIsValidUUID_ReturnFalse","Elapsed":0}
{"Time":"2023-02-13T14:27:16.730981+08:00","Action":"output","Package":"go_traning/unit_test/validator","Output":"FAIL\n"}
{"Time":"2023-02-13T14:27:16.731117+08:00","Action":"output","Package":"go_traning/unit_test/validator","Output":"coverage: 100.0% of statements\n"}
{"Time":"2023-02-13T14:27:16.732104+08:00","Action":"output","Package":"go_traning/unit_test/validator","Output":"FAIL\tgo_traning/unit_test/validator\t0.509s\n"}
{"Time":"2023-02-13T14:27:16.732145+08:00","Action":"fail","Package":"go_traning/unit_test/validator","Elapsed":0.509}
適合本地查看測試狀態,需使用該套件寫斷言
$ go install github.com/smartystreets/goconvey
$ goconvey
2023/02/13 15:01:53 integration.go:122: File system state modified, publishing current folders... 10054679803 10054679830
2023/02/13 15:01:53 goconvey.go:159: Received request from watcher to execute tests...
2023/02/13 15:01:54 executor.go:69: Executor status: 'executing'
2023/02/13 15:01:54 coordinator.go:46: Executing concurrent tests: go_traning/unit_test
2023/02/13 15:01:54 coordinator.go:46: Executing concurrent tests: go_traning/unit_test/db
2023/02/13 15:01:54 coordinator.go:46: Executing concurrent tests: go_traning/unit_test/validator
2023/02/13 15:01:55 shell.go:89: Coverage output: ? go_traning/unit_test [no test files]
2023/02/13 15:01:55 shell.go:91: Run without coverage
2023/02/13 15:01:56 parser.go:24: [passed]: go_traning/unit_test/validator
2023/02/13 15:01:56 parser.go:24: [no test files]: go_traning/unit_test
2023/02/13 15:01:56 parser.go:24: [passed]: go_traning/unit_test/db
2023/02/13 15:01:56 executor.go:69: Executor status: 'idle'
執行測試時,如果被呼叫的那個 func 檔案所在位置的路徑下(包含自身),有 import 內部套件比如DB方法、自訂義logger等等,且當中含有 init()
函式;該函式會於測試中被執行,導致測試會因為找不到連線物件或環境變數而執行失敗,即使被測試的func本身並沒呼叫相關方法也一樣
現在有個 GetExample 非常單純的功能要被測試,但同個檔案內有其他的func會去call DB,所以 import 了 DB
internal/odds.go
import (
"fmt"
"go_traning/unit_test/db"
)
// 要被測試的範例
func GetExample() bool {
return true
}
// 其他不相干的 func
func GetOdds(eventInfo EventInfo) (result []Market, err error) {
// 從DB取得原始資料
rawOdds, _ := db.GetOddsByEventID(eventInfo.SourceEventID)
// 功能邏輯
// ...
DB 資料夾下有 init()
作用於取環境參數後去進行連線,若取不到服務會 panic
db/init.go
package db
import "os"
func init() {
if os.Getenv("DB_HOST") == "" {
panic("缺少環境參數")
}
// DB連線邏輯
// ...
}
幫 GetExample 寫了一個測試案例,內容也很單純
internal/odds_test.go
func TestGetExample(t *testing.T) {
tests := []struct {
name string
want bool
}{
{
"true",
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetExample(); got != tt.want {
t.Errorf("GetExample() = %v, want %v", got, tt.want)
}
})
}
}
執行後…Opps! 怎麼 run 不起來
panic: 缺少環境參數
goroutine 1 [running]:
go_traning/unit_test/db.init.0()
/Users/esther_lin/Desktop/Go traning/goUnitTest/db/init.go:7 +0x47
FAIL go_traning/unit_test/internal/odds_before 0.291s
FAIL
init()
,改為定義好 func 讓 main.go
去 callTDD (Test-Driven Development) 測試驅動開發(入門篇)
30天快速上手TDD系列 第 2 篇
為什麼程式需要單元測試? - 概念篇
[C#][Unit Test] 04. Mock (仿製資料)
go test命令(Go语言测试命令)完全攻略
A Quick Way to Generate Go Tests in Visual Studio Code
Golang Unit Test(二)
Golang Test - 單元測試、Mock與http handler 測試
Go Mock -gomock- 簡明教程
How to write stronger unit tests with a custom go-mock matcher
Testing with GoMock: A Tutorial
跟煎魚學Go - 1.4 使用 Gomock 进行单元测试
SonarQube and code coverage
Go单测从零到溜系列5—goconvey的使用