Golang测试断言库使用指南

Golang测试断言库使用指南 大家好,

我正在学习Go语言,并开始构建一个测试断言库,它能让编写数据断言变得更容易一些。

https://github.com/laliluna/expectations

我非常高兴能获得关于代码和库本身的反馈。

祝好

import (
	"testing"
	"github.com/laliluna/expectations"
)

func TestDemo(t *testing.T) {
	eT := expectations.NewT(t)
	eT.ExpectString("Hello World").EndsWith("joe")

	values := []int{1, 2, 3}
	eT.ExpectSlice(values).Contains(1, 2, 55, 66)

expectations_test.go
--------------------
--- TestDemo in line 15: Expect Hello World to end with joe
--- TestDemo in line 17: Expect [1 2 3] to contain [1 2 55 66] but was missing [55 66]

--- FAIL: TestDemo (0.00s)

更多关于Golang测试断言库使用指南的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

我已为 nil 指针、映射、通道和切片添加了支持。

非常感谢对该库的任何反馈。

干杯

更多关于Golang测试断言库使用指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


看了你的 expectations 库,设计得很清晰。断言链式调用和错误报告格式都很直观。我来分析一下核心实现并提供一些优化思路。

1. 类型断言的设计 你的 ExpectStringExpectSlice 方法返回特定类型的断言对象,这种类型安全的设计很好。可以看看 Contains 方法的实现:

// 当前实现示例
func (es *SliceExpectation[T]) Contains(expected ...T) *SliceExpectation[T] {
    missing := []T{}
    for _, exp := range expected {
        found := false
        for _, actual := range es.actual {
            if reflect.DeepEqual(actual, exp) {
                found = true
                break
            }
        }
        if !found {
            missing = append(missing, exp)
        }
    }
    if len(missing) > 0 {
        es.t.Errorf("Expect %v to contain %v but was missing %v", 
            es.actual, expected, missing)
    }
    return es
}

2. 错误报告优化 当前的错误信息已经很清晰,但可以增加更多上下文。比如在断言失败时显示具体位置:

func (e *Expectation[T]) fail(format string, args ...interface{}) {
    // 获取调用栈信息
    _, file, line, _ := runtime.Caller(2)
    e.t.Errorf("%s:%d: %s", filepath.Base(file), line, 
        fmt.Sprintf(format, args...))
}

3. 扩展更多断言方法 考虑添加这些常用断言:

// 数值比较
func (en *NumberExpectation) GreaterThan(expected float64) *NumberExpectation {
    if en.actual <= expected {
        en.t.Errorf("Expect %v > %v", en.actual, expected)
    }
    return en
}

// 切片长度断言
func (es *SliceExpectation[T]) HasLength(expected int) *SliceExpectation[T] {
    if len(es.actual) != expected {
        es.t.Errorf("Expect length %d but got %d", expected, len(es.actual))
    }
    return es
}

// 映射包含键值
func (em *MapExpectation[K, V]) HasKey(key K) *MapExpectation[K, V] {
    if _, ok := em.actual[key]; !ok {
        em.t.Errorf("Expect map to contain key %v", key)
    }
    return em
}

4. 性能考虑 对于大型切片的 Contains 操作,可以考虑使用 map 来优化查找性能:

func (es *SliceExpectation[T]) ContainsFast(expected ...T) *SliceExpectation[T] {
    // 构建查找集
    lookup := make(map[interface{}]struct{}, len(es.actual))
    for _, v := range es.actual {
        lookup[v] = struct{}{}
    }
    
    missing := []T{}
    for _, exp := range expected {
        if _, found := lookup[exp]; !found {
            missing = append(missing, exp)
        }
    }
    
    if len(missing) > 0 {
        es.t.Errorf("Missing elements: %v", missing)
    }
    return es
}

5. 自定义匹配器 考虑支持自定义匹配器接口:

type Matcher[T any] interface {
    Match(actual T) bool
    String() string
}

func (e *Expectation[T]) ToMatch(matcher Matcher[T]) *Expectation[T] {
    if !matcher.Match(e.actual) {
        e.t.Errorf("Expect %v to match %s", e.actual, matcher.String())
    }
    return e
}

// 使用示例
type RegexMatcher struct {
    pattern *regexp.Regexp
}

func (m *RegexMatcher) Match(actual string) bool {
    return m.pattern.MatchString(actual)
}

func (m *RegexMatcher) String() string {
    return fmt.Sprintf("pattern %s", m.pattern.String())
}

6. 并发安全 如果测试涉及并发,可以添加原子操作支持:

func (e *Expectation[T]) Eventually(matcher Matcher[T], timeout time.Duration) *Expectation[T] {
    deadline := time.Now().Add(timeout)
    for time.Now().Before(deadline) {
        if matcher.Match(e.loadActual()) { // 需要原子加载
            return e
        }
        time.Sleep(10 * time.Millisecond)
    }
    e.t.Errorf("Timeout waiting for condition: %s", matcher.String())
    return e
}

你的库基础很好,错误信息格式特别清晰。继续完善类型支持和性能优化会让它更实用。测试断言库的关键是提供清晰的失败信息和灵活的匹配方式,你已经有了很好的起点。

回到顶部