亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go語(yǔ)言測(cè)試庫(kù)testify使用學(xué)習(xí)

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

簡(jiǎn)介

testify可以說(shuō)是最流行的(從 GitHub star 數(shù)來(lái)看)Go 語(yǔ)言測(cè)試庫(kù)了。testify提供了很多方便的函數(shù)幫助我們做assert和錯(cuò)誤信息輸出。使用標(biāo)準(zhǔn)庫(kù)testing,我們需要自己編寫(xiě)各種條件判斷,根據(jù)判斷結(jié)果決定輸出對(duì)應(yīng)的信息。

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

  • assert:斷言;
  • mock:測(cè)試替身;
  • suite:測(cè)試套件。

準(zhǔn)備工作

本文代碼使用 Go Modules。

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

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

安裝testify庫(kù):

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

assert

assert子庫(kù)提供了便捷的斷言函數(shù),可以大大簡(jiǎn)化測(cè)試代碼的編寫(xiě)。總的來(lái)說(shuō),它將之前需要判斷 + 信息輸出的模式

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

簡(jiǎn)化為一行斷言代碼:

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

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

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

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

$ 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類(lèi)函數(shù)眾多,每種函數(shù)都有兩個(gè)版本,一個(gè)版本是函數(shù)名不帶f的,一個(gè)版本是帶f的,區(qū)別就在于帶f的函數(shù),我們需要指定至少兩個(gè)參數(shù),一個(gè)格式化字符串format,若干個(gè)參數(shù)args

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

實(shí)際上,在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ù)類(lèi)型:

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

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

DirExists

函數(shù)類(lèi)型:

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

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

ElementsMatch

函數(shù)類(lèi)型:

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

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

Empty

函數(shù)類(lèi)型:

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

Empty斷言object是空,根據(jù)object中存儲(chǔ)的實(shí)際類(lèi)型,空的含義不同:

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

EqualError

函數(shù)類(lèi)型:

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

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

EqualValues

函數(shù)類(lèi)型:

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

EqualValues斷言expectedactual相等,或者可以轉(zhuǎn)換為相同的類(lèi)型,并且相等。這個(gè)條件比Equal更寬,Equal()返回trueEqualValues()肯定也返回true,反之則不然。實(shí)現(xiàn)的核心是下面兩個(gè)函數(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) {
    // 嘗試類(lèi)型轉(zhuǎn)換
    return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual)
  }
  return false
}

例如我基于int定義了一個(gè)新類(lèi)型MyInt,它們的值都是 100,Equal()調(diào)用將返回 false,EqualValues()會(huì)返回 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ù)類(lèi)型:

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

Error斷言err不為nil。

ErrorAs

函數(shù)類(lèi)型:

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

ErrorAs斷言err表示的 error 鏈中至少有一個(gè)和target匹配。這個(gè)函數(shù)是對(duì)標(biāo)準(zhǔn)庫(kù)中errors.As的包裝。

ErrorIs

函數(shù)類(lèi)型:

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

ErrorIs斷言err的 error 鏈中有target。

逆斷言

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

Assertions 對(duì)象

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

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

順帶提一句TestingT是一個(gè)接口,對(duì)*testing.T做了一個(gè)簡(jiǎn)單的包裝:

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

require

require提供了和assert同樣的接口,但是遇到錯(cuò)誤時(shí),require直接終止測(cè)試,而assert返回false。

mock

testify提供了對(duì) Mock 的簡(jiǎn)單支持。Mock 簡(jiǎn)單來(lái)說(shuō)就是構(gòu)造一個(gè)仿對(duì)象,仿對(duì)象提供和原對(duì)象一樣的接口,在測(cè)試中用仿對(duì)象來(lái)替換原對(duì)象。這樣我們可以在原對(duì)象很難構(gòu)造,特別是涉及外部資源(數(shù)據(jù)庫(kù),訪(fǎng)問(wèn)網(wǎng)絡(luò)等)。例如,我們現(xiàn)在要編寫(xiě)一個(gè)從一個(gè)站點(diǎn)拉取用戶(hù)列表信息的程序,拉取完成之后程序顯示和分析。如果每次都去訪(fǎng)問(wèn)網(wǎng)絡(luò)會(huì)帶來(lái)極大的不確定性,甚至每次返回不同的列表,這就給測(cè)試帶來(lái)了極大的困難。我們可以使用 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()方法完成爬取和解析操作,返回用戶(hù)列表。為了方便 Mock,GetAndPrintUsers()函數(shù)接受一個(gè)ICrawler接口?,F(xiàn)在來(lái)定義我們的 Mock 對(duì)象,實(shí)現(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)
}

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

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

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

運(yùn)行:

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

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

使用 Mock,我們可以精確斷言某方法以特定參數(shù)的調(diào)用次數(shù),Times(n int),它有兩個(gè)便捷函數(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)
    }
  }
}

編寫(xiě) Mock 對(duì)象:

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)
}

運(yùn)行:

$ 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]

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

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

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

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

suite

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

type SetupAllSuite interface {
  SetupSuite()
}

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

type TearDownAllSuite interface {
  TearDownSuite()
}

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

type SetupTestSuite interface {
  SetupTest()
}

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

type TearDownTestSuite interface {
  TearDownTest()
}

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

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

我們來(lái)編寫(xiě)一個(gè)測(cè)試套件結(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")
}

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

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

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

$ 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

測(cè)試 HTTP 服務(wù)器

Go 標(biāo)準(zhǔn)庫(kù)提供了一個(gè)httptest用于測(cè)試 HTTP 服務(wù)器。現(xiàn)在編寫(xiě)一個(gè)簡(jiǎ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)
  }
}

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

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")
}

運(yùn)行:

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

很簡(jiǎn)單,沒(méi)有問(wèn)題。

但是我們發(fā)現(xiàn)一個(gè)問(wèn)題,上面的很多代碼有重復(fù),recorder/mux等對(duì)象的創(chuàng)建,處理器函數(shù)的注冊(cè)。使用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")
}

最后編寫(xiě)一個(gè)TestXxx驅(qū)動(dòng)測(cè)試:

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

總結(jié)

testify擴(kuò)展了testing標(biāo)準(zhǔn)庫(kù),斷言庫(kù)assert,測(cè)試替身mock和測(cè)試套件suite,讓我們編寫(xiě)測(cè)試代碼更容易!

參考

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

相關(guān)文章

最新評(píng)論