简介
testifytestifyasserttesting
testify
assertmocksuite
准备工作
本文代码使用 Go Modules。
创建目录并初始化:
$ 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判断 + 信息输出的模式
if got != expected { t.Errorf("Xxx failed expect:%d got:%d", got, expected) }
简化为一行断言代码:
assert.Equal(t, got, expected, "they should be equal")
assertassert
func TestEqual(t *testing.T) { var a = 100 var b = 200 assert.Equal(t, a, b, "") }
testifytesting_test.goTestXxxgo 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
我们看到信息更易读。
testifyassertfffformatargs
func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) func Equalf(t TestingT, expected, actual interface{}, msg string, args ...interface{})
Equalf()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...)...) }
f
Contains
函数类型:
func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool
Containsscontainsscontains
DirExists
函数类型:
func DirExists(t TestingT, path string, msgAndArgs ...interface{}) bool
DirExistspathpath
ElementsMatch
函数类型:
func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) bool
ElementsMatchlistAlistBlistA/listB
Empty
函数类型:
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
Emptyobjectobject
nil""
EqualError
函数类型:
func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool
EqualErrortheError.Error()errString
EqualValues
函数类型:
func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
EqualValuesexpectedactualEqualEqual()trueEqualValues()truereflect.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) { // 尝试类型转换 return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) } return false }
intMyIntEqual()EqualValues()
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
函数类型:
func Error(t TestingT, err error, msgAndArgs ...interface{}) bool
Errorerrnil
ErrorAs
函数类型:
func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool
ErrorAserrtargeterrors.As
ErrorIs
函数类型:
func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool
ErrorIserrtarget
逆断言
NotEqual/NotEqualValues
Assertions 对象
TestingTtestify*testing.T*AssertionsAssertionsTestingT
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
requireassertrequireassertfalse
mock
testify
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()GetAndPrintUsers()ICrawlerICrawler
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) }
GetUserList()Mock.Called()Called()mock.ArgumentserrorInt()/String()/Bool()/Error()Get()interface{}
crawler.On("GetUserList").Return(MockUsers, nil)GetUserList()MockUsersnilGetUserList()Arguments.Get(0)Arguments.Error(1)
crawler.AssertExpectations(t)
运行:
$ go test &{dj 18} &{zhangsan 20} PASS ok github.com/darjun/testify 0.258s
GetAndPrintUsers()
Times(n int)Once()/Twice()Hello(n int)
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()<=<
$ go test PASS ok github.com/darjun/testify 0.236s
我们还可以设置以指定参数调用会导致 panic,测试程序的健壮性:
e.On("Hello", 100).Panic("out of range")
suite
testifyTestSuitetestifysuite.Suite
type SetupAllSuite interface { SetupSuite() }
SetupSuite()SetupAllSuiteTearDownAllSuite
type TearDownAllSuite interface { TearDownSuite() }
TearDonwSuite()TearDownSuite
type SetupTestSuite interface { SetupTest() }
SetupTest()SetupTestSuiteTearDownTestSuite
type TearDownTestSuite interface { TearDownTest() }
TearDownTest()TearDownTest
BeforeTest/AfterTest
我们来编写一个测试套件结构作为演示:
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") }
go testTestXxxsuite.Run()
func TestExample(t *testing.T) { suite.Run(t, new(MyTestSuit)) }
suite.Run(t, new(MyTestSuit))MyTestSuitTestXxx
$ 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 服务器
httptest
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) } }
httptestResponseRecorderhttp.ResponseWriter
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
很简单,没有问题。
recorder/muxsuite
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
func TestHTTP(t *testing.T) { suite.Run(t, new(MySuite)) }
总结
testifytestingassertmocksuite
参考
- testify GitHub:github.com/stretchr/testify
- Go 每日一库 GitHub:https://github.com/darjun/go-daily-lib