golang深度测试与灵活比较插件库go-testdeep的使用

Golang深度测试与灵活比较插件库go-testdeep的使用

go-testdeep是一个极其灵活的Golang深度比较库,它扩展了Go的testing包功能。

简介

go-testdeep是一个Golang测试库,它允许进行灵活的数据结构比较,支持从简单到复杂的测试场景。

简单示例

import (
  "testing"
  "github.com/maxatome/go-testdeep/td"
)

func TestMyFunc(t *testing.T) {
  td.Cmp(t, MyFunc(), &Info{Name: "Alice", Age: 42})
}

使用TestDeep运算符的复杂示例

import (
  "testing"
  "github.com/maxatome/go-testdeep/td"
)

func TestMyFunc(t *testing.T) {
  td.Cmp(t, MyFunc(), td.Struct(
    &Info{Name: "Alice"},
    td.StructFields{
      "Age": td.Between(40, 45),
    },
  ))
}

在字面量中使用锚定运算符

import (
  "testing"
  "github.com/maxatome/go-testdeep/td"
)

func TestMyFunc(tt *testing.T) {
  t := td.NewT(tt)

  t.Cmp(MyFunc(), &Info{
    Name: "Alice",
    Age:  t.Anchor(td.Between(40, 45)).(int),
  })
}

HTTP API测试示例

import (
  "testing"
  "time"
  "github.com/maxatome/go-testdeep/helpers/tdhttp"
  "github.com/maxatome/go-testdeep/td"
)

type Person struct {
  ID        uint64    `json:"id"`
  Name      string    `json:"name"`
  Age       int       `json:"age"`
  CreatedAt time.Time `json:"created_at"`
}

func TestMyApi(t *testing.T) {
  var id uint64
  var createdAt time.Time

  testAPI := tdhttp.NewTestAPI(t, myAPI) // ①

  testAPI.PostJSON("/person", Person{Name: "Bob", Age: 42}). // ②
    Name("Create a new Person").
    CmpStatus(http.StatusCreated). // ③
    CmpJSONBody(td.JSON(`
// 注意允许注释
{
  "id":         $id,             // 由API/DB设置
  "name":       "Alice",
  "age":        Between(40, 45), // ④
  "created_at": "$createdAt",    // 由API/DB设置
}`,
      td.Tag("id", td.Catch(&id, td.NotZero())),        // ⑤
      td.Tag("createdAt", td.All(                      // ⑥
        td.HasSuffix("Z"),                             // ⑦
        td.Smuggle(func(s string) (time.Time, error) { // ⑧
          return time.Parse(time.RFC3339Nano, s)
        }, td.Catch(&createdAt, td.Between(testAPI.SentAt(), time.Now()))), // ⑨
      )),
    ))
  if !testAPI.Failed() {
    t.Logf("The new Person ID is %d and was created at %s", id, createdAt)
  }
}

安装

$ go get github.com/maxatome/go-testdeep

辅助工具

tdhttp - HTTP API测试助手

github.com/maxatome/go-testdeep/helpers/tdhttp包提供了一些函数来轻松测试HTTP处理程序。

tdsuite - 测试套件助手

github.com/maxatome/go-testdeep/helpers/tdsuite包以非侵入性的方式为go-testdeep添加了测试套件功能。

tdutil - 助手工具

github.com/maxatome/go-testdeep/helpers/tdutil包允许为go-testdeep助手编写单元测试,并提供了一些有用的函数。

错误输出示例

当测试不匹配时,go-testdeep会生成详细的错误输出,帮助开发者快速定位问题。

许可证

go-testdeep在源代码树的根目录中的LICENSE文件下发布BSD样式的许可证。


更多关于golang深度测试与灵活比较插件库go-testdeep的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang深度测试与灵活比较插件库go-testdeep的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Go-TestDeep: Golang深度测试与灵活比较插件库

Go-TestDeep 是一个强大的 Golang 测试工具库,它扩展了标准库 testing 的功能,提供了更灵活、更深入的测试比较能力。下面我将详细介绍它的使用方法和优势。

为什么需要 Go-TestDeep

标准库的 testing 包虽然功能完善,但在复杂数据结构的比较上存在一些局限性:

  • 深层嵌套结构比较不够直观
  • 错误信息不够详细
  • 部分比较场景需要手动编写大量代码

Go-TestDeep 解决了这些问题,提供了更强大的断言功能。

安装

go get github.com/maxatome/go-testdeep

基本使用

简单比较

import (
    "testing"
    td "github.com/maxatome/go-testdeep"
)

func TestSimple(t *testing.T) {
    got := 42
    td.Cmp(t, got, 42) // 成功
    td.Cmp(t, got, 43) // 失败
}

灵活匹配

func TestFlexible(t *testing.T) {
    got := []int{1, 2, 3}
    
    // 检查切片是否包含特定元素,顺序不重要
    td.Cmp(t, got, td.Bag(3, 1, 2))
    
    // 检查切片是否包含至少一个大于2的元素
    td.Cmp(t, got, td.Contains(td.Gt(2)))
}

高级特性

结构体比较

type User struct {
    ID        int
    Name      string
    CreatedAt time.Time
}

func TestStruct(t *testing.T) {
    user := User{
        ID:   1,
        Name: "Alice",
        CreatedAt: time.Now(),
    }
    
    // 忽略时间字段的比较
    td.Cmp(t, user, td.Struct(
        User{
            ID:   1,
            Name: "Alice",
        },
        td.StructFields{
            "CreatedAt": td.Ignore(),
        },
    ))
}

JSON 比较

func TestJSON(t *testing.T) {
    got := `{"name":"Bob","age":30,"hobbies":["reading","swimming"]}`
    
    td.CmpJSON(t, got, `{
        "name": "Bob",
        "age": td.Between(30, 40),
        "hobbies": td.ArrayEach(td.String()),
    }`)
}

自定义匹配器

func TestCustom(t *testing.T) {
    got := "Hello, 世界"
    
    // 自定义匹配器:检查字符串是否是有效的UTF-8
    td.Cmp(t, got, td.Code(func(s string) bool {
        return utf8.ValidString(s)
    }))
}

常用操作符

Go-TestDeep 提供了丰富的操作符:

  1. 基本比较

    • td.Eq(val): 严格等于
    • td.Not(val): 不等于
    • td.Gt(val), td.Gte(val), td.Lt(val), td.Lte(val): 大于/小于比较
  2. 集合操作

    • td.Bag(items...): 无序集合匹配
    • td.Set(items...): 无序集合匹配,不考虑重复
    • td.SubSetOf(items...): 子集匹配
    • td.SuperSetOf(items...): 超集匹配
  3. 类型检查

    • td.Isa(typ): 检查类型
    • td.Struct(model, fields): 结构体匹配
  4. 特殊匹配

    • td.Re(pattern): 正则匹配
    • td.Smuggle(fn, expected): 转换后匹配
    • td.Lax(expected): 宽松类型匹配

最佳实践

  1. 清晰的错误信息

    td.Cmp(t, getUser(), td.Struct(
        User{Name: "Alice"},
    ), "check user details")
    
  2. 组合匹配器

    td.Cmp(t, getResult(), td.All(
        td.Struct(Result{Status: "success"}),
        td.Smuggle(func(r Result) int { return len(r.Items) }, td.Gte(1)),
    ))
    
  3. 表格驱动测试

    func TestAdd(t *testing.T) {
        cases := []struct {
            a, b int
            expected interface{}
        }{
            {1, 2, 3},
            {0, 0, 0},
            {-1, 1, td.Between(0, 1)},
        }
        
        for _, tc := range cases {
            td.Cmp(t, Add(tc.a, tc.b), tc.expected)
        }
    }
    

性能考虑

Go-TestDeep 虽然功能强大,但在性能敏感的场景下需要注意:

  • 复杂匹配器会比简单比较消耗更多资源
  • 在热路径测试中考虑使用标准库的简单比较
  • 可以结合 testing.B 进行基准测试

总结

Go-TestDeep 为 Golang 测试提供了:

  1. 更丰富的比较操作
  2. 更清晰的错误信息
  3. 更灵活的匹配方式
  4. 更好的可维护性

对于复杂数据结构的测试,它能显著减少样板代码,提高测试的可读性和可维护性。对于简单的单元测试,标准库可能更合适,但对于集成测试和复杂场景,Go-TestDeep 是一个强大的工具。

回到顶部