国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

GoLang 單元測試打樁和 mock

這篇具有很好參考價值的文章主要介紹了GoLang 單元測試打樁和 mock。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

目錄

什么是 mock

變量打樁

接口方法/Redis

函數(shù)/方法打樁

包函數(shù)

成員方法

MySQL

sqlmock

sqlite mock gorm

http mock


源碼地址

單測基礎(chǔ)

什么是 mock

? ? ? ?單元測試,顧名思義對某個單元函數(shù)進(jìn)行測試,被測函數(shù)本身中用到的變量、函數(shù)、資源不應(yīng)被測試代碼依賴,所謂 mock,就是想辦法通過 “虛擬” 代碼替換掉依賴的方法和資源,一般需要 mock 掉以下依賴:

  • 變量

  • 函數(shù)/方法

  • MySQL

  • Redis

  • http 調(diào)用

變量打樁

有時我們的代碼里依賴一個全局變量,測試方法根據(jù)全局變量的不同值執(zhí)行不同的邏輯,那么可以用 gostub?對變量進(jìn)行打樁。

?global.go:

package main

var size = 5

func Size() int {
	if size > 10 {
		return 10
	}
	return size
}
package main

import (
    "testing"

    "github.com/agiledragon/gomonkey/v2"
    "github.com/prashantv/gostub"
)

func TestSizeStub(t *testing.T) {
    tests := []struct {
        name string
        want int
        f    func() *gostub.Stubs
    }{
        {name: "size > 10", want: 10, f: func() *gostub.Stubs {
            return gostub.Stub(&size, 11)
        }},
        {name: "size <= 10", want: 3, f: func() *gostub.Stubs {
            return gostub.Stub(&size, 3)
        }},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            stub := tt.f()
            if got := Size(); got != tt.want {
                t.Errorf("Size() = %v, want %v", got, tt.want)
            }
            stub.Reset()
        })
    }
}

func TestSizeMonkey(t *testing.T) {
    tests := []struct {
        name string
        want int
        f    func() *gomonkey.Patches
    }{
        {name: "size > 10", want: 10, f: func() *gomonkey.Patches {
            return gomonkey.ApplyGlobalVar(&size, 11)
        }},
        {name: "size <= 10", want: 3, f: func() *gomonkey.Patches {
            return gomonkey.ApplyGlobalVar(&size, 3)
        }},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            stub := tt.f()
            if got := Size(); got != tt.want {
                t.Errorf("Size() = %v, want %v", got, tt.want)
            }
            stub.Reset()
        })
    }
}
$ go test -v -cover
=== RUN   TestSize
=== RUN   TestSize/size_>_10
=== RUN   TestSize/size_<=_10
--- PASS: TestSize (0.00s)
    --- PASS: TestSize/size_>_10 (0.00s)
    --- PASS: TestSize/size_<=_10 (0.00s)
PASS
coverage: 100.0% of statements

接口方法/Redis

首先 Go 語言推薦的是面向接口編程,所以官方提供并推薦使用? gomock? 對依賴的方法進(jìn)行 mock,前提是依賴的方法是通過抽象接口實現(xiàn)的,gomock 執(zhí)行過程如下:

  1. 使用mockgen為你想要mock的接口生成一個mock。

  2. 在你的測試代碼中,創(chuàng)建一個gomock.Controller實例并把它作為參數(shù)傳遞給mock對象的構(gòu)造函數(shù)來創(chuàng)建一個mock對象。

  3. 調(diào)用EXPECT()為你的mock對象設(shè)置各種期望和返回值。

  4. 調(diào)用mock控制器的Finish()以驗證mock的期望行為。

gomock 常用方法:

類型

用法

作用

參數(shù)

gomock.Any(v)

匹配任何類型

gomock.Eq(v)

匹配使用反射?reflect.DeepEqual 與 v 相等的值

gomock.Not(v)

v 不是 Matcher 時,匹配使用反射?reflect.DeepEqual 與 v 不相等的值;v 是 Matcher 時,匹配和 Macher 不匹配的值(Matcher)

gomock.Nil()

匹配等于 nil 的值

返回

Return()

mock 方法返回值

Do(func)

傳入的 func 在 mock 真正被調(diào)用時自動執(zhí)行,忽略 Return,比如:對調(diào)用方法的參數(shù)進(jìn)行校驗

DoAndReturn(func)

傳入的 func 在 mock 真正被調(diào)用時自動執(zhí)行,對應(yīng) func 返回值作為 mock 方法返回值

調(diào)用次數(shù)

AnyTimes(n int)

mock 方法可以被調(diào)用任意次數(shù),一次不調(diào)用也不會失敗(這里大家可以自檢一下各自的單測代碼,用這個方法的單測可能并沒有按照預(yù)期運行

Times()

mock 方法被調(diào)用次數(shù),次數(shù)不相等運行失敗

MaxTimes(n int)

mock 方法被調(diào)用次數(shù),大于規(guī)定次數(shù)運行失敗

MinTimes(n int)

mock 方法被調(diào)用次數(shù),小于規(guī)定次數(shù)運行失敗

調(diào)用排序

gomock.InOrder(

first.EXPECT.Func().Return(),

second.EXPECT.Func().Return(),

thrid.EXPECT.Func().Return(),

)

規(guī)定多個 mock 方法的調(diào)用順序,順序不符運行失敗

first := rc.EXPECT().DoFucn()

second := rc.EXPECT().DoFunc().After(first)

規(guī)定多個 mock 方法的先后依賴關(guān)系,順序不符運行失敗

首先通過 mockgen 生成 Redis Client 的 mock 代碼:

$ go get -u github.com/golang/mock/gomock
$ go install github.com/golang/mock/mockgen

本地interface:
mockgen[go run -mod=mod github.com/golang/mock/mockgen -package mock] -source ~/go/pkg/mod/github.com/opentracing/opentracing-go\@v1.2.0/tracer.go -destination ./opentracing/tracer.go?Tracer
遠(yuǎn)端interface:
go run -mod=mod github.com/golang/mock/mockgen -package redis -destination ./mock/redis/redis.go  github.com/go-redis/redis/v8 Cmdable

redis.go:

package main

import (
	"context"

	"github.com/go-redis/redis/v8"
)

func handleRedis(c redis.Cmdable) (string, error) {
	return c.Get(context.Background(), "redis").Result()
}

func conn() *redis.Client {
	return redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
}
package main

import (
	"testing"

	"github.com/go-redis/redis/v8"
	"github.com/golang/mock/gomock"
)

func Test_handleRedis(t *testing.T) {
	ctl := gomock.NewController(t)
	defer ctl.Finish()

	c := NewMockCmdable(ctl)
	c.EXPECT().Get(gomock.Any(), gomock.Any()).Times(1).Return(redis.NewStringResult("redis", nil))

	handleRedis(c)
}

函數(shù)/方法打樁

假如我們依賴的其他人寫的方法,并不是通過接口實現(xiàn)的,無法使用 gomock 時,可以用 gomonkey 進(jìn)行打樁

包函數(shù)

常用函數(shù):

  • gomonkey.ApplyFunc():單個包函數(shù)打樁

  • gomonkey.ApplyFuncSeq():連續(xù)多個包函數(shù)打樁

func.go

package main

func A() int {
	return B()
}

func AA() int {
	return B() + B()
}

func B() int {
	return 0
}
package main

import (
	"testing"

	"github.com/agiledragon/gomonkey/v2"
	"github.com/stretchr/testify/assert"
)

// TestA 函數(shù),單次打樁
func TestA(t *testing.T) {
	patch := gomonkey.ApplyFunc(B, func() int {
		return 1
	})
	defer patch.Reset()

	assert.Equal(t, 1, A())
}

// TestAA 函數(shù),連續(xù)打樁
func TestAA(t *testing.T) {
	patch := gomonkey.ApplyFuncSeq(B, []gomonkey.OutputCell{
		{Values: gomonkey.Params{1}},
		{Values: gomonkey.Params{2}},
	})
	defer patch.Reset()

	assert.Equal(t, 3, AA())
}

成員方法

常用函數(shù):

  • gomonkey.ApplyMethod():單個公有成員方法打樁

  • patch.ApplyPrivateMethod():單個私有成員方法打樁

  • patch.ApplyMethodSeq():連續(xù)多個公有成員方法打樁

  • gomonkey.ApplyFuncSeq():連續(xù)多個私有成員方法打樁

method.go

package main

type S struct{}

func (s *S) A() int {
	return s.B() + s.b()
}

func (s *S) AA() int {
	return s.B() + s.b() + s.B() + s.b()
}

func (s *S) B() int {
	return 0
}

func (s *S) b() int {
	return 0
}
package main

import (
	"reflect"
	"testing"

	"github.com/agiledragon/gomonkey/v2"
	"github.com/stretchr/testify/assert"
)

// TestS_AA 成員方法單個打樁
func TestS_A(t *testing.T) {
	s := &S{}

	// 公共成員方法
	patch := gomonkey.ApplyMethod(reflect.TypeOf(s), "B", func(_ *S) int {
		return 1
	})
	// 私有成員方法
	patch.ApplyPrivateMethod(reflect.TypeOf(s), "b", func(_ *S) int {
		return 2
	})
	defer patch.Reset()

	assert.Equal(t, 3, s.A())
}

// TestS_AA 成員方法連續(xù)打樁
func TestS_AA(t *testing.T) {
	s := &S{}

	// 私有成員方法
	patch := gomonkey.ApplyFuncSeq((*S).b, []gomonkey.OutputCell{
		{Values: gomonkey.Params{1}},
		{Values: gomonkey.Params{2}},
	})
	// 公共成員方法
	patch.ApplyMethodSeq(reflect.TypeOf(s), "B", []gomonkey.OutputCell{
		{Values: gomonkey.Params{1}},
		{Values: gomonkey.Params{2}},
	})
	defer patch.Reset()

	assert.Equal(t, 6, s.AA())
}

MySQL

sqlmock

db.go

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
)

const dsn = "root:123456@tcp(127.0.0.1:3306)/test"

type Test struct {
	ID      int64  `json:"id" db:"id" gorm:"column:id"`
	GoodsID int64  `json:"goodsID" db:"goods_id" gorm:"column:goods_id"`
	Name    string `json:"name" db:"name" gorm:"column:name"`
}

func (Test) TableName() string {
	return "test"
}

func handle(db *sql.DB) (err error) {
	tx, err := db.Begin()
	if err != nil {
		return
	}

	defer func() {
		switch err {
		case nil:
			err = tx.Commit()
		default:
			tx.Rollback()
		}
	}()

	rows, err := tx.Query("SELECT * from test where id > ?", 0)
	if err != nil {
		panic(err)
	}
	result := []Test{}
	if err = sqlx.StructScan(rows, &result); err != nil {
		panic(err)
	}

	b, err := json.Marshal(result)
	if err != nil {
		panic(err)
	}
	fmt.Println("sql:", string(b))

	if _, err = tx.Exec("UPDATE test SET goods_id = goods_id + 1 where id = 2"); err != nil {
		return
	}
	if _, err = tx.Exec("INSERT INTO test (goods_id, name) VALUES (?, ?)", 1, "1"); err != nil {
		return
	}
	return
}

func main() {
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		panic(err)
	}
	defer db.Close()

	if err = handle(db); err != nil {
		panic(err)
	}
}
package main

import (
	"log"
	"os"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	_ "github.com/go-sql-driver/mysql"
	"github.com/stretchr/testify/assert"
)

func Test_handle(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		panic(err)
	}

	mock.ExpectBegin()
	// (.+) 用于替代字段,可用于 select、order、group等
	mock.ExpectQuery("SELECT (.+) from test where id > ?").WillReturnRows(sqlmock.NewRows([]string{"id", "goods_id", "name"}).AddRow(1, 1, "1"))
    // sql前綴匹配
	mock.ExpectExec("UPDATE test SET goods_id").WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectExec("INSERT INTO test").WithArgs(1, "1").WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectCommit()

	if err = handle(db); err != nil {
		panic(err)
	}

	if err = mock.ExpectationsWereMet(); err != nil {
		panic(err)
	}
}

sqlite mock gorm

如果遇到如下錯誤:

/usr/local/go16/pkg/tool/linux_amd64/link: running gcc failed: exit status 1
/usr/bin/ld: /tmp/go-link-866330658/000020.o(.text+0x74): unresolvable H??@?>H??FH??H??H??@?~?F?H??@?~H??8?H??H??0?FH??H??(?FH??H?? ?FH??H???FH??H???FH??H??F?fD relocation against symbol `stderr@@GLIBC_2.2.5'
/usr/bin/ld: BFD version 2.20.51.0.2-5.34.el6 20100205 internal error, aborting at reloc.c line 443 in bfd_get_reloc_size
/usr/bin/ld: Please report this bug.
collect2: ld returned 1 exit status

更新 go env gcc 版本:
go env -w CC=/opt/compiler/gcc-8.2/bin/gcc
go env -w CXX=/opt/compiler/gcc-8.2/bin/g++

或

CC=/opt/compiler/gcc-8.2/bin/gcc CXX=/opt/compiler/gcc-8.2/bin/g++ go test -c -cover 

db.go

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

const dsn = "root:123456@tcp(127.0.0.1:3306)/test"

type Test struct {
	ID      int64  `json:"id" db:"id" gorm:"column:id"`
	GoodsID int64  `json:"goodsID" db:"goods_id" gorm:"column:goods_id"`
	Name    string `json:"name" db:"name" gorm:"column:name"`
}

func (Test) TableName() string {
	return "test"
}

func main() {
	orm, err := gorm.Open(mysql.Open(dsn))
	if err != nil {
		panic(err)
	}

	handleOrm(orm)
}

func handleOrm(orm *gorm.DB) {
	var rows []Test

	clause := func(db *gorm.DB) *gorm.DB {
		return db.Where("id >= ?", 1)
	}
	err := clause(orm.Select("*")).Find(&rows).Error
	if err != nil {
		panic(err)
	}

	b, err := json.Marshal(rows)
	if err != nil {
		panic(err)
	}
	fmt.Println("gorm", string(b))
}
package main

import (
	"log"
	"os"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

func Test_handleOrm(t *testing.T) {
	db := NewMemoryDB()
	err := db.Migrator().CreateTable(&Test{})
	assert.Nil(t, err)

	handleOrm(db)
}

func NewMemoryDB() *gorm.DB {
	var db *gorm.DB
	var err error
	newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
		logger.Config{
			SlowThreshold: time.Second, // 慢 SQL 閾值
			LogLevel:      logger.Info, // Log level
			Colorful:      false,       // 禁用彩色打印
		},
	)
	dialector := sqlite.Open(":memory:?cache=shared")
	if db, err = gorm.Open(dialector, &gorm.Config{
		Logger: newLogger,
	}); err != nil {
		panic(err)
	}
	dba, err := db.DB()
	dba.SetMaxOpenConns(1)
	return db
}

func CloseMemoryDB(db *gorm.DB) {
	sqlDB, _ := db.DB()
	sqlDB.Close()
}

http mock

http.go文章來源地址http://www.zghlxwxcb.cn/news/detail-787909.html

package main

import (
	"fmt"
	"net/http"
	"time"
)

func Send() (err error) {
	req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:8080", nil)
	if err != nil {
		return
	}
	client := &http.Client{
		Timeout: time.Second,
	}
	resp, err := client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("HTTP status is %d", resp.StatusCode)
	}

	return
}
package main

import (
	"net/http"
	"testing"

	"github.com/jarcoal/httpmock"
	"github.com/smartystreets/goconvey/convey"
	"github.com/stretchr/testify/assert"
)

func TestSend(t *testing.T) {
	convey.Convey("TestSend", t, func() {
		convey.Convey("success", func() {
			httpmock.Activate()
			defer httpmock.DeactivateAndReset()
			httpmock.RegisterResponder(http.MethodGet, "https://127.0.0.1:8080", httpmock.NewStringResponder(http.StatusOK, ""))

			err := Send()
			assert.Nil(t, err)
		})
	})
}

到了這里,關(guān)于GoLang 單元測試打樁和 mock的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 詳解JUnit單元測試框架(打樁測試待更新)示例代碼有格式問題,待更新

    詳解JUnit單元測試框架(打樁測試待更新)示例代碼有格式問題,待更新

    單元測試負(fù)責(zé)對最小的軟件設(shè)計單元(模塊)進(jìn)行驗證,根據(jù)軟件設(shè)計文檔中對模塊功能的描述,對重要的程序分支進(jìn)行測試并發(fā)現(xiàn)錯誤。 對于單元測試框架來講,它主要完成以下幾件事。 提供用例組織與執(zhí)行: 測試用例只有幾條時,可以不考慮用例組織,但是用例達(dá)到成

    2024年02月05日
    瀏覽(31)
  • 【PowerMockito:編寫單元測試過程中采用when打樁失效的問題】

    【PowerMockito:編寫單元測試過程中采用when打樁失效的問題】

    正如上圖所示,采用when打樁了,但是,實際執(zhí)行的時候還是返回null。 打樁時直接用any() 但是這樣可能出現(xiàn)一個mybatisplus的異常,所以在測試類中需要加入以下代碼片段:

    2024年02月02日
    瀏覽(19)
  • 使用 gomonkey Mock 函數(shù)及方法

    在 Golang 語言中,寫單元測試的時候,不可避免的會涉及到對其他函數(shù)及方法的 Mock,即在假設(shè)其他函數(shù)及方法響應(yīng)預(yù)期結(jié)果的同時,校驗被測函數(shù)的響應(yīng)是否符合預(yù)期。 其中,在 Mock 其他函數(shù)及方法的時候,我們常用到的一個測試類庫是「gomonkey」。特別地,對于方法和函數(shù)

    2024年02月01日
    瀏覽(13)
  • mock打樁不生效的問題

    mock打樁不生效的問題

    ------------------我是分割線----------------------- 向大佬請教了一下,本質(zhì)的原因如下 1. mock的目的是為了排除外部依賴,你只管傳過來一個該方法需要的參數(shù)類型,就可以。 2. 我在mock里寫的Path.of,debug的時候跟蹤內(nèi)存地址發(fā)現(xiàn),在業(yè)務(wù)代碼里并不是這個對象,所以打樁無效;而你

    2024年02月11日
    瀏覽(19)
  • Service層代碼單元測試以及單元測試如何Mock

    Service層代碼單元測試以及單元測試如何Mock

    接著上一篇文章:單元測試入門篇,本篇文章作為單元測試的進(jìn)階篇,主要介紹如何對Springboot Service層代碼做單元測試,以及單元測試中涉及外調(diào)服務(wù)時,如何通過Mock完成測試。 現(xiàn)在項目都流行前后端代碼分離,后端使用springboot框架,在service層編寫接口代碼實現(xiàn)邏輯。假設(shè)

    2023年04月08日
    瀏覽(18)
  • java的單元測試-mock測試

    對于普通的方法,通常采用斷言測試。 對于接口,需要使用mockMvc 對于未開發(fā)的功能,需要mockBean模擬一個業(yè)務(wù)bean java自身攜帶的工具類,也可以用于一些對拋出異常要求不高的業(yè)務(wù)或者存在全局異常的項目 另外有一個更加簡單的寫法,以assert開頭 曾使用注入方式得到mockM

    2023年04月08日
    瀏覽(29)
  • 單元測試junit+mock

    單元測試junit+mock

    單元測試(unit testing),是指對軟件中的最小可測試單元進(jìn)行檢查和驗證。至于“單元”的大小或范圍,并沒有一個明確的標(biāo)準(zhǔn),“單元”可以是一個方法、類、功能模塊或者子系統(tǒng)。 單元測試通常和白盒測試聯(lián)系到一起 ,如果單從概念上來講兩者是有區(qū)別的,不過我們通

    2024年02月08日
    瀏覽(36)
  • Testify Mock 單元測試

    Testify 提供了單測方便的斷言能力,這里的斷言是將對代碼實際返回的斷言,代碼的實際輸出和預(yù)期是否一致。下面是 gin-gonic/gin 代碼庫的單測代碼,Testify 還提供了很多其他的方法: 單元測試中也會存在不穩(wěn)定的代碼,我們的入?yún)㈦m然保持不變,但每次單測的結(jié)果可能會發(fā)

    2024年02月03日
    瀏覽(25)
  • 單元測試與Mock

    單元測試與Mock

    作者:一笑欽陳 郵箱:xianqin_chen@163.com 你好,我是一笑欽陳,《零零后程序員成長之路》作者,一線互聯(lián)網(wǎng) Java 工程師。很高興你閱讀我的博客,讓我們共同成長進(jìn)步! 提醒:在接下來您對本博客的閱讀中,如果遇到一些內(nèi)容、圖稿、代碼等中的勘誤都可以通過郵件進(jìn)行反

    2024年02月08日
    瀏覽(46)
  • mock寫單元測試和查數(shù)據(jù)庫的單元測試

    mock寫單元測試和查數(shù)據(jù)庫的單元測試

    一:mock方式 在測試類上添加注解 將需要測試的類bean添加進(jìn)來,該類中的其他bean也添加進(jìn)來 給被測試類中用到的參數(shù)、返回值類創(chuàng)建對象 創(chuàng)建BeforeEach和AfterEach方法,在BeforeEach方法中給參數(shù),返回值設(shè)置值 然后在test方法中設(shè)置被測試的方法 二:可以檢測dao層sql的單元測試

    2024年02月15日
    瀏覽(19)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包