找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

581

积分

0

好友

75

主题
发表于 前天 08:19 | 查看: 8| 回复: 0

testify 测试框架中,assert 包应该是在日常测试过程中使用最多的。它提供了丰富的断言函数,能帮助我们高效地进行单元测试和结果验证。

assert 包概览

testify 提供的 assert 函数很多,每种函数签名都有两个版本:一个函数名f,另一个不带f。主要区别在于,带 f 的方法需要指定至少两个参数,一个格式化字符串 format 和若干个参数 args。通过这种方式,你可以在测试运行失败之后,给出自定义的、更清晰的错误信息。你可以根据自身情况来决定使用哪一种形式的断言。

EqualEqualf 为例,它们的函数签名如下:

func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
    if h, ok := t.(tHelper); ok {
        h.Helper()
    }
    if err := validateEqualArgs(expected, actual); err != nil {
        return Fail(t, fmt.Sprintf("Invalid operation: %#v == %#v (%s)",
            expected, actual, err), msgAndArgs...)
    }

    if !ObjectsAreEqual(expected, actual) {
        diff := diff(expected, actual)
        expected, actual = formatUnequalValues(expected, actual)
        return Fail(t, fmt.Sprintf("Not equal: \n"+
            "expected: %s\n"+
            "actual  : %s%s", expected, actual, diff), msgAndArgs...)
    }

    return true
}

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

一个简单的使用示例如下所示:

func TestAddUInt64(t *testing.T) {
    var expectValue uint64 = 30
    actualValue := Add[uint64](10, 21)
    assert.Equalf(t, expectValue, actualValue, "expect value %d actual value %d", expectValue, actualValue)
}

常用断言函数解析

下面我们来详细看看 assert 包中一些最常用的函数。

Equal & NotEqual

Equal 主要用于判断两个值是否严格相等,是使用频率最高的断言之一。testify 支持两种调用方式:一种是传统的 assert.Equal(t, expected, actual, msg),另一种是先创建 assert 对象 assert := assert.New(t),再调用 assert.Equal(expected, actual, msg)。示例如下:

import (
    "testing"

    "github.com/stretchr/testify/assert"
)

// 第一种断言方式
func TestAddInt(t *testing.T) {
    expectValue := 30
    actualValue := Add(10, 20)
    assert.Equal(t, expectValue, actualValue, "expect value and actual value should be equal")
}

// 第二种断言方式
func TestAddFloat32(t *testing.T) {
    assert := assert.New(t)
    var expectValue float32 = 30.03
    actualValue := Add[float32](10.01, 20.02)
    // 注意这里的区别
    assert.Equal(expectValue, actualValue, "expect value and actual value should be equal")
}

func TestAddUInt32(t *testing.T) {
    var expectValue uint32 = 30
    actualValue := Add[uint32](10, 20)
    assert.NotEqualf(t, expectValue, actualValue, "expect value %d should not equal actual value %d", expectValue, actualValue)
}

Contains & NotContains

Contains 用于判断是否存在包含关系。它的函数签名是 func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool。其中,参数 s 可以是字符串、数组/切片或 map,相应地 contains 应为子串、数组/切片元素或 map 的键。NotContains 则相反,用于判断是否不存在包含关系。

func TestContainsStr(t *testing.T) {
    a, b := "Surpass", "Evan"
    assert.Containsf(t, a, b, "%s should contains %s", a, b)
}

func TestContaisArray(t *testing.T) {
    a, b := [...]int{1, 2, 3, 4, 5}, [...]int{1, 2}
    assert.Containsf(t, a, b, "%v should contains %v", a, b)
}

func TestNotContaisArray(t *testing.T) {
    a, b := [...]int{1, 2, 3, 4, 5}, 1
    assert.NotContainsf(t, a, b, "%v should not contains %v", a, b)
}

DirExists & NoDirExists

这两个函数用于判断某个目录是否存在。DirExists 断言目录存在,NoDirExists 断言目录不存在。

func TestDirExist(t *testing.T) {
    // 目录不存在
    path := "F:\\tmp"
    assert.DirExistsf(t, path, "%s should exist", path)
}

func TestDirNotExist(t *testing.T) {
    // 目录不存在
    path := "F:\\tmp"
    assert.NoDirExists(t, path, "%s should not exist", path)
}

ElementsMatch

ElementsMatch 用于判断两个数组或切片是否包含相同的元素集合,但忽略元素出现的顺序。需要注意的是,如果存在重复的元素,则重复的次数也必须相等。

func TestElementsMatch(t *testing.T) {
    a := [...]int{1, 2, 3, 4, 2}
    b := [...]int{1, 2, 2, 3, 4}
    c := [...]int{1, 2, 2, 2, 3}
    assert.ElementsMatchf(t, a, b, "a:%v should match b:%v", a, b)
    assert.ElementsMatchf(t, a, c, "a:%v should match c:%v", a, c) // 此断言会失败
}

Empty & Nil

Empty 用于判断给定的值是否为其类型的“零值”或逻辑上的“空”(如空字符串、空切片、nil等)。而 Nil 则专用于断言一个对象的值是否为 nil

func TestIsEmpty(t *testing.T) {
    a := [...]int{1}
    assert.Emptyf(t, a, "a value %v should be empty", a)
    b := 0
    assert.Emptyf(t, b, "b value %v should be empty", b)
    c := struct{}{}
    assert.Emptyf(t, c, "b value %v should be empty", c)
}

func TestObjIsNil(t *testing.T) {
    f := func(a, b int) (int, error) {
        if b == 0 {
            return math.MaxInt, errors.New("b is equal 0")
        }
        return a / b, nil
    }

    t.Run("返回错误为nil-使用Nil来断言", func(t *testing.T) {
        _, err := f(10, 2)
        assert.Nil(t, err)
    })

    t.Run("返回错误为nil-使用Empty来断言", func(t *testing.T) {
        _, err := f(10, 2)
        assert.Empty(t, err)
    })
}

EqualValues

EqualValues 也用于判断两个值是否“相等”,但它比 Equal 更宽松。如果两个值的类型不同,EqualValues 会尝试将它们转换为更高精度的公共类型后再进行比较(例如 int32int64)。

func TestEqualValues(t *testing.T) {
    var a int32 = 30
    var b int64 = 30
    var c float64 = 30.0
    assert.EqualValuesf(t, a, b, "a type %T value %d should equal %T value %d", a, a, b, b)
    assert.EqualValuesf(t, a, c, "a type %T value %d should equal %T value %d", a, a, c, c)
}

Error & NoError & EqualError

Go中处理错误是常态,assert 也提供了相应的断言。Error 断言返回的 error 不为 nilNoError 断言返回的 errornilEqualError 则断言返回的 error 的字符串描述等于指定的字符串。

func TestError(t *testing.T) {
    nilError := func() error {
        return nil
    }
    errSample := func() error {
        return errors.New("some error happend")
    }
    assert.Error(t, errSample(), "error is  not nil")
    assert.NoError(t, nilError(), "some error happend")
}

func TestEqualError(t *testing.T) {
    errFunc := func() error {
        return errors.New("error sample test")
    }
    assert.EqualError(t, errFunc(), "error sample test")
}

ErrorAs & ErrorIs

这两个函数用于处理更复杂的错误链判断。

  • ErrorAs:判断返回的错误是否可以被转换为指定的目标错误类型。常用于检查错误链中是否包裹了特定类型的错误。
  • ErrorIs:判断返回的错误是否等于(使用 errors.Is)指定的错误值。常用于检查错误链中是否包含某个特定的 sentinel error。

使用标准库错误的示例:

func TestErrorAs(t *testing.T) {
    var pathError *os.PathError
    _, err := os.Open("/tmp/surpass.txt")

    assert.ErrorAsf(t, err, &pathError, "expect error %T,acutal error %T", pathError, err)
}

func TestErrorIs(t *testing.T) {
    _, err := os.Open("/tmp/surpass.txt")
    assert.ErrorIs(t, err, os.ErrNotExist, "...")
}

也可以用于自定义错误类型的断言:

// 自定义错误类型
type MyError struct {
    Code    int
    Message string
}

// 实现Error方法
func (me *MyError) Error() string {
    return fmt.Sprintf("code %d - %s", me.Code, me.Message)
}

func MockError() error {
    innerError := &MyError{
        Code:    404,
        Message: "Not Found",
    }
    return fmt.Errorf("Mock error:%w", innerError)
}

func TestErrorAs(t *testing.T) {
    var myError *MyError
    err := MockError()
    assert.ErrorAs(t, err, &myError, "")
}

// 自定义错误类型
var ErrNotFound = errors.New("Not Found")
var ErrPermission = errors.New("Permission Denied")

func TestErrorAs(t *testing.T) {
    f := func(id int) error {
        if id == 0 {
            return ErrNotFound
        } else if id < 0 {
            return errors.Join(ErrPermission, ErrNotFound)
        }
        return nil
    }

    t.Run("返回特定错误", func(t *testing.T) {
        assert.ErrorIs(t, f(0), ErrNotFound)
    })

    t.Run("返回多个错误", func(t *testing.T) {
        assert.ErrorIs(t, f(-5), ErrPermission)
    })

    t.Run("没有错误,使用Empty判断", func(t *testing.T) {
        assert.Empty(t, f(10))

    })

    t.Run("没有错误,使用ErrorIs判断", func(t *testing.T) {
        assert.ErrorIs(t, f(10), nil)
    })
}

通过掌握这些核心的 assert 断言函数,你可以为你的 Go 项目编写出更健壮、更清晰的单元测试代码,从而有效提升代码质量和开发效率。如果你想了解更多关于测试框架或最佳实践,欢迎在云栈社区与其他开发者交流探讨。




上一篇:C++函数传参:指针与引用深度对比及选择策略
下一篇:Palantir为何难以复制?深入剖析其技术架构与数据治理护城河
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 02:49 , Processed in 0.465149 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表