您现在的位置是:网站首页> 编程资料编程资料

Go语言测试库testify使用学习_Golang_

2023-05-26 417人已围观

简介 Go语言测试库testify使用学习_Golang_

简介

testify可以说是最流行的(从 GitHub star 数来看)Go 语言测试库了。testify提供了很多方便的函数帮助我们做assert和错误信息输出。使用标准库testing,我们需要自己编写各种条件判断,根据判断结果决定输出对应的信息。

testify核心有三部分内容:

  • assert:断言;
  • mock:测试替身;
  • suite:测试套件。

准备工作

本文代码使用 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")

结构更清晰,更可读。熟悉其他语言测试框架的开发者对assert的相关用法应该不会陌生。此外,assert中的函数会自动生成比较清晰的错误描述信息:

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

使用testify编写测试代码与testing一样,测试文件为_test.go,测试函数为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类函数众多,每种函数都有两个版本,一个版本是函数名不带f的,一个版本是带f的,区别就在于带f的函数,我们需要指定至少两个参数,一个格式化字符串format,若干个参数args

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

Contains断言s包含contains。其中s可以是字符串,数组/切片,map。相应地,contains为子串,数组/切片元素,map 的键。

DirExists

函数类型:

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

DirExists断言路径path是一个目录,如果path不存在或者是一个文件,断言失败。

ElementsMatch

函数类型:

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

ElementsMatch断言listAlistB包含相同的元素,忽略元素出现的顺序。listA/listB必须是数组或切片。如果有重复元素,重复元素出现的次数也必须相等。

Empty

函数类型:

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

Empty断言object是空,根据object中存储的实际类型,空的含义不同:

  • 指针:nil
  • 整数:0;
  • 浮点数:0.0;
  • 字符串:空串""
  • 布尔:false;
  • 切片或 channel:长度为 0。

EqualError

函数类型:

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

EqualError断言theError.Error()的返回值与errString相等。

EqualValues

函数类型:

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

EqualValues断言expectedactual相等,或者可以转换为相同的类型,并且相等。这个条件比Equal更宽,Equal()返回trueEqualValues()肯定也返回true,反之则不然。实现的核心是下面两个函数,使用了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) { // 尝试类型转换 return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) } return false }

例如我基于int定义了一个新类型MyInt,它们的值都是 100,Equal()调用将返回 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

函数类型:

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

Error断言err不为nil

ErrorAs

函数类型:

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

ErrorAs断言err表示的 error 链中至少有一个和target匹配。这个函数是对标准库中errors.As的包装。

ErrorIs

函数类型:

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

ErrorIs断言err的 error 链中有target

逆断言

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

Assertions 对象

观察到上面的断言都是以TestingT为第一个参数,需要大量使用时比较麻烦。testify提供了一种方便的方式。先以*testing.T创建一个*Assertions对象,Assertions定义了前面所有的断言方法,只是不需要再传入TestingT参数了。

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 简单来说就是构造一个仿对象,仿对象提供和原对象一样的接口,在测试中用仿对象来替换原对象。这样我们可以在原对象很难构造,特别是涉及外部资源(数据库,访问网络等)。例如,我们现在要编写一个从一个站点拉取用户列表信息的程序,拉取完成之后程序显示和分析。如果每次都去访问网络会带来极大的不确定性,甚至每次返回不同的列表,这就给测试带来了极大的困难。我们可以使用 Mock 技术。

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() ([]*U
                
                

-六神源码网