首頁 > 軟體

使用 gomonkey Mock 函數及方法範例詳解

2022-06-02 18:01:15

前言

在 Golang 語言中,寫單元測試的時候,不可避免的會涉及到對其他函數及方法的 Mock,即在假設其他函數及方法響應預期結果的同時,校驗被測函數的響應是否符合預期。

其中,在 Mock 其他函數及方法的時候,我們常用到的一個測試類庫是「gomonkey」。特別地,對於方法和函數的 Mock,略有差異,在這裡我們就分別給出函數和方法 Mock 範例,方便大家參考。

函數

在 Golang 語言中,函數是沒有接受者的方法,其形式為

func function_name([parameter list]) [return_types] {
   函數體
}

對於函數的 Mock 相對來說比較簡單,假設我們對 A 函數進行單元測試,且 A 函數裡面又呼叫了 B 函數,例如

func A(ctx context.Context, str string) error {
   if len(str) == 0 {
	  return errors.New("str is empty")
   }
   return test_package_name.B(ctx, str)
}

為了將 A 函數的每一行程式碼都覆蓋到,則其單元測試可以寫為:

func TestA(t *testing.T) {
	type args struct {
		ctx    context.Context
		str    string
	}
	tests := []struct {
		name    string
		args    args
		Setup   func(t *testing.T)
		wantErr error
	}{
		{
			name: "len(str) == 0",
			wantErr: errors.New("str is empty")
		},
		{
			name: "正常響應",
			Setup: func(t *testing.T) {
				patches := gomonkey.ApplyFunc(test_package_name.B, func(_ context.Context, _ string) error {
					return nil
				})
				t.Cleanup(func() {
					patches.Reset()
				})
			},
			args: args{
				ctx:     context.Background(),
				str:     "test",
			},
			wantErr: nil,
		},
	}

	// 執行測試用例
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.Setup != nil {
				tt.Setup(t)
			}
			err := A(tt.args.ctx, tt.args.str)
			if err != nil {
				assert.EqualError(t, err, tt.wantErr.Error(), "error 不符合預期")
			}
		})
	}
}

其中,ApplyFunc函數是用來 Mock 函數的,其第一個引數為需要 Mock 的函數名稱(不需要寫參數列),第二個引數為需要 Mock 的函數結果;特別地,在Setup裡面,我們要記得顯式呼叫Cleanuppatches進行Reset操作,防止該 Mock 影響其他測試用例。

方法

在 Golang 語言中,方法是含有接受者的函數,其形式為

func (variable_name variable_data_type) function_name([parameter list]) [return_type]{
   函數體
}

對於方法的 Mock 相對來說複雜一下,假設我們對 A 函數進行單元測試,且 A 函數裡面又呼叫了結構 C 的 B 方法,例如

func A(ctx context.Context, str string) error {
   if len(str) == 0 {
	  return errors.New("str is empty")
   }
   c := &test_package_name.C{}
   return c.B(ctx, str)
}

為了將 A 函數的每一行程式碼都覆蓋到,則其單元測試可以寫為:

func TestA(t *testing.T) {
	// 初始化C結構
	var c *test_package_name.C
	
	type args struct {
		ctx    context.Context
		str    string
	}
	tests := []struct {
		name    string
		args    args
		Setup   func(t *testing.T)
		wantErr error
	}{
		{
			name: "len(str) == 0",
			wantErr: errors.New("str is empty")
		},
		{
			name: "正常響應",
			Setup: func(t *testing.T) {
				patches := gomonkey.ApplyMethod(reflect.TypeOf(c), "B", func(_ *test_package_name.C, _ context.Context, _ string) error {
					return nil
				})
				t.Cleanup(func() {
					patches.Reset()
				})
			},
			args: args{
				ctx:     context.Background(),
				str:     "test",
			},
			wantErr: nil,
		},
	}

	// 執行測試用例
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.Setup != nil {
				tt.Setup(t)
			}
			err := A(tt.args.ctx, tt.args.str)
			if err != nil {
				assert.EqualError(t, err, tt.wantErr.Error(), "error 不符合預期")
			}
		})
	}
}

其中,ApplyMethod函數是用來 Mock 方法的,其第一個引數為需要 Mock 的方法的接受者型別,第二個引數為需要 Mock 的方法名稱(字串型別),第三個引數為需要 Mock 的方法的定義及 Mock 結果;特別地,第一個引數和第三個引數需要我們注意:

  • 第一個引數,需要使用reflect.TypeOf獲取接受者的型別,初始化的接受者必須是真正的型別,如結構 C 組合了結構 D,而B方法是通過組合 D 得到的,則初始化的時候需要定義結構 D,而不是結構 C,否則會報空指標異常;
  • 第三個引數,雖然B方法的宣告是func(ctx context.Context, str string),但是在使用ApplyMethod的時候,需要將B方法的宣告修改為func(c *test_package_name.C, ctx context.Context, str string),即需要將方法的接受者置為方法的第一個引數。

參考

還有就是,大家在使用gomonkey的時候,有可能遇到許可權校驗的問題以及非 Debug 模式執行失敗的問題,可以參考:

golang使用gomonkey和monkey來mock方法或者函數時報panic: permission denied

使用 gomonkey 遇到非 debug 模式執行失敗的問題及解決方法

到這裡,本文就要結束了,希望對大家有所幫助。

到此這篇關於使用 gomonkey Mock 函數及方法的文章就介紹到這了,更多相關gomonkey Mock 函數內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com