欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語言測試庫testify使用學(xué)習

 更新時間:2022年07月22日 17:14:18   作者:darjun  
這篇文章主要為大家介紹了Go語言測試庫testify的使用學(xué)習示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

簡介

testify可以說是最流行的(從 GitHub star 數(shù)來看)Go 語言測試庫了。testify提供了很多方便的函數(shù)幫助我們做assert和錯誤信息輸出。使用標準庫testing,我們需要自己編寫各種條件判斷,根據(jù)判斷結(jié)果決定輸出對應(yīng)的信息。

testify核心有三部分內(nèi)容:

  • assert:斷言;
  • mock:測試替身;
  • suite:測試套件。

準備工作

本文代碼使用 Go Modules。

創(chuàng)建目錄并初始化:

$ mkdir -p testify && cd testify
$ go mod init github.com/darjun/go-daily-lib/testify

安裝testify庫:

$ go get -u github.com/stretchr/testify

assert

assert子庫提供了便捷的斷言函數(shù),可以大大簡化測試代碼的編寫??偟膩碚f,它將之前需要判斷 + 信息輸出的模式

if got != expected {
  t.Errorf("Xxx failed expect:%d got:%d", got, expected)
}

簡化為一行斷言代碼:

assert.Equal(t, got, expected, "they should be equal")

結(jié)構(gòu)更清晰,更可讀。熟悉其他語言測試框架的開發(fā)者對assert的相關(guān)用法應(yīng)該不會陌生。此外,assert中的函數(shù)會自動生成比較清晰的錯誤描述信息:

func TestEqual(t *testing.T) {
  var a = 100
  var b = 200
  assert.Equal(t, a, b, "")
}

使用testify編寫測試代碼與testing一樣,測試文件為_test.go,測試函數(shù)為TestXxx。使用go test命令運行測試:

$ go test

--- FAIL: TestEqual (0.00s)
    assert_test.go:12:
                Error Trace:
                Error:          Not equal:
                                expected: 100
                                actual  : 200
                Test:           TestEqual
FAIL
exit status 1
FAIL    github.com/darjun/go-daily-lib/testify/assert   0.107s

我們看到信息更易讀。

testify提供的assert類函數(shù)眾多,每種函數(shù)都有兩個版本,一個版本是函數(shù)名不帶f的,一個版本是帶f的,區(qū)別就在于帶f的函數(shù),我們需要指定至少兩個參數(shù),一個格式化字符串format,若干個參數(shù)args

func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{})
func Equalf(t TestingT, expected, actual interface{}, msg string, args ...interface{})

實際上,在Equalf()函數(shù)內(nèi)部調(diào)用了Equal()

func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
  if h, ok := t.(tHelper); ok {
    h.Helper()
  }
  return Equal(t, expected, actual, append([]interface{}{msg}, args...)...)
}

所以,我們只需要關(guān)注不帶f的版本即可。

Contains

函數(shù)類型:

func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool

Contains斷言s包含contains。其中s可以是字符串,數(shù)組/切片,map。相應(yīng)地,contains為子串,數(shù)組/切片元素,map 的鍵。

DirExists

函數(shù)類型:

func DirExists(t TestingT, path string, msgAndArgs ...interface{}) bool

DirExists斷言路徑path是一個目錄,如果path不存在或者是一個文件,斷言失敗。

ElementsMatch

函數(shù)類型:

func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) bool

ElementsMatch斷言listAlistB包含相同的元素,忽略元素出現(xiàn)的順序。listA/listB必須是數(shù)組或切片。如果有重復(fù)元素,重復(fù)元素出現(xiàn)的次數(shù)也必須相等。

Empty

函數(shù)類型:

func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool

Empty斷言object是空,根據(jù)object中存儲的實際類型,空的含義不同:

  • 指針:nil;
  • 整數(shù):0;
  • 浮點數(shù):0.0;
  • 字符串:空串"";
  • 布爾:false;
  • 切片或 channel:長度為 0。

EqualError

函數(shù)類型:

func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool

EqualError斷言theError.Error()的返回值與errString相等。

EqualValues

函數(shù)類型:

func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool

EqualValues斷言expectedactual相等,或者可以轉(zhuǎn)換為相同的類型,并且相等。這個條件比Equal更寬,Equal()返回trueEqualValues()肯定也返回true,反之則不然。實現(xiàn)的核心是下面兩個函數(shù),使用了reflect.DeapEqual()

func ObjectsAreEqual(expected, actual interface{}) bool {
  if expected == nil || actual == nil {
    return expected == actual
  }
  exp, ok := expected.([]byte)
  if !ok {
    return reflect.DeepEqual(expected, actual)
  }
  act, ok := actual.([]byte)
  if !ok {
    return false
  }
  if exp == nil || act == nil {
    return exp == nil && act == nil
  }
  return bytes.Equal(exp, act)
}
func ObjectsAreEqualValues(expected, actual interface{}) bool {
    // 如果`ObjectsAreEqual`返回 true,直接返回
  if ObjectsAreEqual(expected, actual) {
    return true
  }
  actualType := reflect.TypeOf(actual)
  if actualType == nil {
    return false
  }
  expectedValue := reflect.ValueOf(expected)
  if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) {
    // 嘗試類型轉(zhuǎn)換
    return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual)
  }
  return false
}

例如我基于int定義了一個新類型MyInt,它們的值都是 100,Equal()調(diào)用將返回 false,EqualValues()會返回 true:

type MyInt int
func TestEqual(t *testing.T) {
  var a = 100
  var b MyInt = 100
  assert.Equal(t, a, b, "")
  assert.EqualValues(t, a, b, "")
}

Error

函數(shù)類型:

func Error(t TestingT, err error, msgAndArgs ...interface{}) bool

Error斷言err不為nil。

ErrorAs

函數(shù)類型:

func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool

ErrorAs斷言err表示的 error 鏈中至少有一個和target匹配。這個函數(shù)是對標準庫中errors.As的包裝。

ErrorIs

函數(shù)類型:

func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool

ErrorIs斷言err的 error 鏈中有target

逆斷言

上面的斷言都是它們的逆斷言,例如NotEqual/NotEqualValues等。

Assertions 對象

觀察到上面的斷言都是以TestingT為第一個參數(shù),需要大量使用時比較麻煩。testify提供了一種方便的方式。先以*testing.T創(chuàng)建一個*Assertions對象,Assertions定義了前面所有的斷言方法,只是不需要再傳入TestingT參數(shù)了。

func TestEqual(t *testing.T) {
  assertions := assert.New(t)
  assertion.Equal(a, b, "")
  // ...
}

順帶提一句TestingT是一個接口,對*testing.T做了一個簡單的包裝:

type TestingT interface{
  Errorf(format string, args ...interface{})
}

require

require提供了和assert同樣的接口,但是遇到錯誤時,require直接終止測試,而assert返回false。

mock

testify提供了對 Mock 的簡單支持。Mock 簡單來說就是構(gòu)造一個仿對象,仿對象提供和原對象一樣的接口,在測試中用仿對象來替換原對象。這樣我們可以在原對象很難構(gòu)造,特別是涉及外部資源(數(shù)據(jù)庫,訪問網(wǎng)絡(luò)等)。例如,我們現(xiàn)在要編寫一個從一個站點拉取用戶列表信息的程序,拉取完成之后程序顯示和分析。如果每次都去訪問網(wǎng)絡(luò)會帶來極大的不確定性,甚至每次返回不同的列表,這就給測試帶來了極大的困難。我們可以使用 Mock 技術(shù)。

package main
import (
  "encoding/json"
  "fmt"
  "io/ioutil"
  "net/http"
)
type User struct {
  Name string
  Age  int
}
type ICrawler interface {
  GetUserList() ([]*User, error)
}
type MyCrawler struct {
  url string
}
func (c *MyCrawler) GetUserList() ([]*User, error) {
  resp, err := http.Get(c.url)
  if err != nil {
    return nil, err
  }
  defer resp.Body.Close()
  data, err := ioutil.ReadAll(resp.Body)
  if err != nil {
    return nil, err
  }
  var userList []*User
  err = json.Unmarshal(data, &userList)
  if err != nil {
    return nil, err
  }
  return userList, nil
}
func GetAndPrintUsers(crawler ICrawler) {
  users, err := crawler.GetUserList()
  if err != nil {
    return
  }
  for _, u := range users {
    fmt.Println(u)
  }
}

Crawler.GetUserList()方法完成爬取和解析操作,返回用戶列表。為了方便 Mock,GetAndPrintUsers()函數(shù)接受一個ICrawler接口?,F(xiàn)在來定義我們的 Mock 對象,實現(xiàn)ICrawler接口:

package main
import (
  "github.com/stretchr/testify/mock"
  "testing"
)
type MockCrawler struct {
  mock.Mock
}
func (m *MockCrawler) GetUserList() ([]*User, error) {
  args := m.Called()
  return args.Get(0).([]*User), args.Error(1)
}
var (
  MockUsers []*User
)
func init() {
  MockUsers = append(MockUsers, &User{"dj", 18})
  MockUsers = append(MockUsers, &User{"zhangsan", 20})
}
func TestGetUserList(t *testing.T) {
  crawler := new(MockCrawler)
  crawler.On("GetUserList").Return(MockUsers, nil)
  GetAndPrintUsers(crawler)
  crawler.AssertExpectations(t)
}

實現(xiàn)GetUserList()方法時,需要調(diào)用Mock.Called()方法,傳入?yún)?shù)(示例中無參數(shù))。Called()會返回一個mock.Arguments對象,該對象中保存著返回的值。它提供了對基本類型和error的獲取方法Int()/String()/Bool()/Error(),和通用的獲取方法Get(),通用方法返回interface{},需要類型斷言為具體類型,它們都接受一個表示索引的參數(shù)。

crawler.On("GetUserList").Return(MockUsers, nil)是 Mock 發(fā)揮魔法的地方,這里指示調(diào)用GetUserList()方法的返回值分別為MockUsersnil,返回值在上面的GetUserList()方法中被Arguments.Get(0)Arguments.Error(1)獲取。

最后crawler.AssertExpectations(t)對 Mock 對象做斷言。

運行:

$ go test
&{dj 18}
&{zhangsan 20}
PASS
ok      github.com/darjun/testify       0.258s

GetAndPrintUsers()函數(shù)功能正常執(zhí)行,并且我們通過 Mock 提供的用戶列表也能正確獲取。

使用 Mock,我們可以精確斷言某方法以特定參數(shù)的調(diào)用次數(shù),Times(n int),它有兩個便捷函數(shù)Once()/Twice()。下面我們要求函數(shù)Hello(n int)要以參數(shù) 1 調(diào)用 1次,參數(shù) 2 調(diào)用兩次,參數(shù) 3 調(diào)用 3 次:

type IExample interface {
  Hello(n int) int
}
type Example struct {
}
func (e *Example) Hello(n int) int {
  fmt.Printf("Hello with %d\n", n)
  return n
}
func ExampleFunc(e IExample) {
  for n := 1; n <= 3; n++ {
    for i := 0; i <= n; i++ {
      e.Hello(n)
    }
  }
}

編寫 Mock 對象:

type MockExample struct {
  mock.Mock
}
func (e *MockExample) Hello(n int) int {
  args := e.Mock.Called(n)
  return args.Int(0)
}
func TestExample(t *testing.T) {
  e := new(MockExample)
  e.On("Hello", 1).Return(1).Times(1)
  e.On("Hello", 2).Return(2).Times(2)
  e.On("Hello", 3).Return(3).Times(3)
  ExampleFunc(e)
  e.AssertExpectations(t)
}

運行:

$ go test

--- FAIL: TestExample (0.00s)
panic:
assert: mock: The method has been called over 1 times.
        Either do one more Mock.On("Hello").Return(...), or remove extra call.
        This call was unexpected:
                Hello(int)
                0: 1
        at: [equal_test.go:13 main.go:22] [recovered]

原來ExampleFunc()函數(shù)中<=應(yīng)該是<導(dǎo)致多調(diào)用了一次,修改過來繼續(xù)運行:

$ go test
PASS
ok      github.com/darjun/testify       0.236s

我們還可以設(shè)置以指定參數(shù)調(diào)用會導(dǎo)致 panic,測試程序的健壯性:

e.On("Hello", 100).Panic("out of range")

suite

testify提供了測試套件的功能(TestSuite),testify測試套件只是一個結(jié)構(gòu)體,內(nèi)嵌一個匿名的suite.Suite結(jié)構(gòu)。測試套件中可以包含多個測試,它們可以共享狀態(tài),還可以定義鉤子方法執(zhí)行初始化和清理操作。鉤子都是通過接口來定義的,實現(xiàn)了這些接口的測試套件結(jié)構(gòu)在運行到指定節(jié)點時會調(diào)用對應(yīng)的方法。

type SetupAllSuite interface {
  SetupSuite()
}

如果定義了SetupSuite()方法(即實現(xiàn)了SetupAllSuite接口),在套件中所有測試開始運行前調(diào)用這個方法。對應(yīng)的是TearDownAllSuite

type TearDownAllSuite interface {
  TearDownSuite()
}

如果定義了TearDonwSuite()方法(即實現(xiàn)了TearDownSuite接口),在套件中所有測試運行完成后調(diào)用這個方法。

type SetupTestSuite interface {
  SetupTest()
}

如果定義了SetupTest()方法(即實現(xiàn)了SetupTestSuite接口),在套件中每個測試執(zhí)行前都會調(diào)用這個方法。對應(yīng)的是TearDownTestSuite

type TearDownTestSuite interface {
  TearDownTest()
}

如果定義了TearDownTest()方法(即實現(xiàn)了TearDownTest接口),在套件中每個測試執(zhí)行后都會調(diào)用這個方法。

還有一對接口BeforeTest/AfterTest,它們分別在每個測試運行前/后調(diào)用,接受套件名和測試名作為參數(shù)。

我們來編寫一個測試套件結(jié)構(gòu)作為演示:

type MyTestSuit struct {
  suite.Suite
  testCount uint32
}
func (s *MyTestSuit) SetupSuite() {
  fmt.Println("SetupSuite")
}
func (s *MyTestSuit) TearDownSuite() {
  fmt.Println("TearDownSuite")
}
func (s *MyTestSuit) SetupTest() {
  fmt.Printf("SetupTest test count:%d\n", s.testCount)
}
func (s *MyTestSuit) TearDownTest() {
  s.testCount++
  fmt.Printf("TearDownTest test count:%d\n", s.testCount)
}
func (s *MyTestSuit) BeforeTest(suiteName, testName string) {
  fmt.Printf("BeforeTest suite:%s test:%s\n", suiteName, testName)
}
func (s *MyTestSuit) AfterTest(suiteName, testName string) {
  fmt.Printf("AfterTest suite:%s test:%s\n", suiteName, testName)
}
func (s *MyTestSuit) TestExample() {
  fmt.Println("TestExample")
}

這里只是簡單在各個鉤子函數(shù)中打印信息,統(tǒng)計執(zhí)行完成的測試數(shù)量。由于要借助go test運行,所以需要編寫一個TestXxx函數(shù),在該函數(shù)中調(diào)用suite.Run()運行測試套件:

func TestExample(t *testing.T) {
  suite.Run(t, new(MyTestSuit))
}

suite.Run(t, new(MyTestSuit))會將運行MyTestSuit中所有名為TestXxx的方法。運行:

$ go test
SetupSuite
SetupTest test count:0
BeforeTest suite:MyTestSuit test:TestExample
TestExample
AfterTest suite:MyTestSuit test:TestExample
TearDownTest test count:1
TearDownSuite
PASS
ok      github.com/darjun/testify       0.375s

測試 HTTP 服務(wù)器

Go 標準庫提供了一個httptest用于測試 HTTP 服務(wù)器?,F(xiàn)在編寫一個簡單的 HTTP 服務(wù)器:

func index(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, "Hello World")
}
func greeting(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "welcome, %s", r.URL.Query().Get("name"))
}
func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/", index)
  mux.HandleFunc("/greeting", greeting)
  server := &http.Server{
    Addr:    ":8080",
    Handler: mux,
  }
  if err := server.ListenAndServe(); err != nil {
    log.Fatal(err)
  }
}

很簡單。httptest提供了一個ResponseRecorder類型,它實現(xiàn)了http.ResponseWriter接口,但是它只是記錄寫入的狀態(tài)碼和響應(yīng)內(nèi)容,不會發(fā)送響應(yīng)給客戶端。這樣我們可以將該類型的對象傳給處理器函數(shù)。然后構(gòu)造服務(wù)器,傳入該對象來驅(qū)動請求處理流程,最后測試該對象中記錄的信息是否正確:

func TestIndex(t *testing.T) {
  recorder := httptest.NewRecorder()
  request, _ := http.NewRequest("GET", "/", nil)
  mux := http.NewServeMux()
  mux.HandleFunc("/", index)
  mux.HandleFunc("/greeting", greeting)
  mux.ServeHTTP(recorder, request)
  assert.Equal(t, recorder.Code, 200, "get index error")
  assert.Contains(t, recorder.Body.String(), "Hello World", "body error")
}
func TestGreeting(t *testing.T) {
  recorder := httptest.NewRecorder()
  request, _ := http.NewRequest("GET", "/greeting", nil)
  request.URL.RawQuery = "name=dj"
  mux := http.NewServeMux()
  mux.HandleFunc("/", index)
  mux.HandleFunc("/greeting", greeting)
  mux.ServeHTTP(recorder, request)
  assert.Equal(t, recorder.Code, 200, "greeting error")
  assert.Contains(t, recorder.Body.String(), "welcome, dj", "body error")
}

運行:

$ go test
PASS
ok      github.com/darjun/go-daily-lib/testify/httptest 0.093s

很簡單,沒有問題。

但是我們發(fā)現(xiàn)一個問題,上面的很多代碼有重復(fù),recorder/mux等對象的創(chuàng)建,處理器函數(shù)的注冊。使用suite我們可以集中創(chuàng)建,省略這些重復(fù)的代碼:

type MySuite struct {
  suite.Suite
  recorder *httptest.ResponseRecorder
  mux      *http.ServeMux
}
func (s *MySuite) SetupSuite() {
  s.recorder = httptest.NewRecorder()
  s.mux = http.NewServeMux()
  s.mux.HandleFunc("/", index)
  s.mux.HandleFunc("/greeting", greeting)
}
func (s *MySuite) TestIndex() {
  request, _ := http.NewRequest("GET", "/", nil)
  s.mux.ServeHTTP(s.recorder, request)
  s.Assert().Equal(s.recorder.Code, 200, "get index error")
  s.Assert().Contains(s.recorder.Body.String(), "Hello World", "body error")
}
func (s *MySuite) TestGreeting() {
  request, _ := http.NewRequest("GET", "/greeting", nil)
  request.URL.RawQuery = "name=dj"
  s.mux.ServeHTTP(s.recorder, request)
  s.Assert().Equal(s.recorder.Code, 200, "greeting error")
  s.Assert().Contains(s.recorder.Body.String(), "welcome, dj", "body error")
}

最后編寫一個TestXxx驅(qū)動測試:

func TestHTTP(t *testing.T) {
  suite.Run(t, new(MySuite))
}

總結(jié)

testify擴展了testing標準庫,斷言庫assert,測試替身mock和測試套件suite,讓我們編寫測試代碼更容易!

參考

以上就是Go語言測試庫testify使用學(xué)習的詳細內(nèi)容,更多關(guān)于Go語言測試庫testify的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語言常見錯誤之誤用init函數(shù)實例解析

    Go語言常見錯誤之誤用init函數(shù)實例解析

    Go語言中的init函數(shù)為開發(fā)者提供了一種在程序正式運行前初始化包級變量的機制,然而,由于init函數(shù)的特殊性,不當?shù)厥褂盟赡芤鹨幌盗袉栴},本文將深入探討如何有效地使用init函數(shù),列舉常見誤用并提供相應(yīng)的避免策略
    2024-01-01
  • Go語言題解LeetCode724尋找數(shù)組的中心下標

    Go語言題解LeetCode724尋找數(shù)組的中心下標

    這篇文章主要為大家介紹了Go語言題解LeetCode724尋找數(shù)組的中心下標,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • Go?Wails開發(fā)桌面應(yīng)用使用示例探索

    Go?Wails開發(fā)桌面應(yīng)用使用示例探索

    這篇文章主要為大家介紹了Go?Wails的使用示例探索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • go語言實現(xiàn)簡易比特幣系統(tǒng)之交易簽名及校驗功能

    go語言實現(xiàn)簡易比特幣系統(tǒng)之交易簽名及校驗功能

    這篇文章主要介紹了go語言實現(xiàn)簡易比特幣系統(tǒng)之交易簽名及校驗功能,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • Go 語言單例模式示例詳解

    Go 語言單例模式示例詳解

    這篇文章主要為大家介紹了Go 語言單例模式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • Golang?template?包基本原理分析

    Golang?template?包基本原理分析

    這篇文章主要為大家介紹了Golang?template?包基本原理分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-09-09
  • go基于Gin框架的HTTP接口限速實踐

    go基于Gin框架的HTTP接口限速實踐

    HTTP接口在各個業(yè)務(wù)模塊之間扮演著重要的角色,本文主要介紹了go基于Gin框架的HTTP接口限速實踐,具有一定的參考價值,感興趣的可以了解一下
    2023-09-09
  • 解決Goland 提示 Unresolved reference 錯誤的問題

    解決Goland 提示 Unresolved reference 錯誤的問題

    這篇文章主要介紹了解決Goland 提示 Unresolved reference 錯誤的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go語言編程實現(xiàn)支持六種級別的日志庫?

    Go語言編程實現(xiàn)支持六種級別的日志庫?

    這篇文章主要為大家介紹了使用Golang編寫一個支持六種級別的日志庫示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-05-05
  • 自動生成代碼controller?tool的簡單使用

    自動生成代碼controller?tool的簡單使用

    這篇文章主要為大家介紹了自動生成代碼controller?tool的簡單使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-05-05

最新評論