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
我已为 nil 指针、映射、通道和切片添加了支持。
非常感谢对该库的任何反馈。
干杯
更多关于Golang测试断言库使用指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
看了你的 expectations 库,设计得很清晰。断言链式调用和错误报告格式都很直观。我来分析一下核心实现并提供一些优化思路。
1. 类型断言的设计
你的 ExpectString 和 ExpectSlice 方法返回特定类型的断言对象,这种类型安全的设计很好。可以看看 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
}
你的库基础很好,错误信息格式特别清晰。继续完善类型支持和性能优化会让它更实用。测试断言库的关键是提供清晰的失败信息和灵活的匹配方式,你已经有了很好的起点。

