golang实现简单模拟时钟功能的测试工具插件库clockwork的使用

Golang实现简单模拟时钟功能的测试工具插件库clockwork的使用

clockwork是一个简单的Go语言假时钟库,用于测试时间相关的功能。

使用方法

clockwork.Clock接口替换time包的使用。

例如,不使用直接的time.Sleep

func myFunc() {
	time.Sleep(3 * time.Second)
	doSomething()
}

而是注入一个clock并使用它的Sleep方法:

func myFunc(clock clockwork.Clock) {
	clock.Sleep(3 * time.Second)
	doSomething()
}

测试示例

你可以很容易地用FakeClock测试myFunc

func TestMyFunc(t *testing.T) {
	ctx := context.Background()
	c := clockwork.NewFakeClock()

	// 启动我们的睡眠函数
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		myFunc(c)
		wg.Done()
	}()

	// 确保我们等待直到myFunc在等待时钟
	// 使用context避免永远阻塞
	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()
	c.BlockUntilContext(ctx, 1)

	assertState()

	// 将FakeClock向前推进时间
	c.Advance(3 * time.Second)

	// 等待函数完成
	wg.Wait()

	assertState()
}

在生产环境中,只需注入真实时钟:

myFunc(clockwork.NewRealClock())

完整示例

下面是一个完整的示例demo:

package main

import (
	"context"
	"fmt"
	"sync"
	"testing"
	"time"

	"github.com/jonboulle/clockwork"
)

// 模拟一个需要等待的函数
func delayedFunction(clock clockwork.Clock, duration time.Duration) {
	fmt.Println("开始等待...")
	clock.Sleep(duration)
	fmt.Println("等待结束!")
}

func TestDelayedFunction(t *testing.T) {
	// 创建假时钟
	fakeClock := clockwork.NewFakeClock()
	
	// 使用WaitGroup等待goroutine完成
	var wg sync.WaitGroup
	wg.Add(1)
	
	// 启动测试函数
	go func() {
		delayedFunction(fakeClock, 5*time.Second)
		wg.Done()
	}()
	
	// 确保函数已经开始执行并进入Sleep状态
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()
	fakeClock.BlockUntilContext(ctx, 1)
	
	// 快进时间
	fmt.Println("快进5秒...")
	fakeClock.Advance(5 * time.Second)
	
	// 等待函数完成
	wg.Wait()
	
	fmt.Println("测试完成")
}

func main() {
	// 生产环境使用真实时钟
	realClock := clockwork.NewRealClock()
	fmt.Println("使用真实时钟:")
	delayedFunction(realClock, 2*time.Second)
	
	// 运行测试
	fmt.Println("\n运行测试:")
	TestDelayedFunction(&testing.T{})
}

特性说明

  1. NewFakeClock() - 创建一个新的假时钟实例
  2. Advance(duration) - 将假时钟向前推进指定时间
  3. BlockUntilContext(ctx, n) - 阻塞直到有n个goroutine在等待时钟
  4. NewRealClock() - 创建一个真实时钟实例(包装标准time包)

这个库特别适合测试那些依赖于时间或定时器的代码,可以避免在测试中实际等待很长时间。


更多关于golang实现简单模拟时钟功能的测试工具插件库clockwork的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang实现简单模拟时钟功能的测试工具插件库clockwork的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang 模拟时钟库 Clockwork 使用指南

Clockwork 是一个用于 Go 语言的测试工具库,它允许你在测试中模拟时间,非常适合需要控制时间因素的单元测试场景。

安装 Clockwork

go get github.com/jonboulle/clockwork

基本使用

真实时钟

package main

import (
	"fmt"
	"time"
	
	"github.com/jonboulle/clockwork"
)

func main() {
	// 使用真实时钟
	realClock := clockwork.NewRealClock()
	
	fmt.Println("当前时间:", realClock.Now())
	
	// 真实时钟的 Sleep 会实际休眠
	realClock.Sleep(time.Second * 2)
	fmt.Println("2秒后:", realClock.Now())
}

模拟时钟

package main

import (
	"fmt"
	"testing"
	"time"
	
	"github.com/jonboulle/clockwork"
)

func TestWithFakeClock(t *testing.T) {
	// 创建模拟时钟
	fakeClock := clockwork.NewFakeClock()
	startTime := fakeClock.Now()
	
	fmt.Println("初始时间:", startTime)
	
	// 向前推进时间
	fakeClock.Advance(time.Hour * 2)
	fmt.Println("2小时后:", fakeClock.Now())
	
	// 设置特定时间
	fakeClock.Set(startTime.Add(time.Hour * 24))
	fmt.Println("设置为24小时后:", fakeClock.Now())
}

高级用法

定时器模拟

func TestTimer(t *testing.T) {
	fakeClock := clockwork.NewFakeClock()
	
	// 创建一个定时器,1小时后触发
	timer := fakeClock.NewTimer(time.Hour)
	
	// 立即检查定时器是否触发
	select {
	case <-timer.Chan():
		t.Error("定时器不应该立即触发")
	default:
		// 预期行为
	}
	
	// 推进时间1小时
	fakeClock.Advance(time.Hour)
	
	// 再次检查定时器
	select {
	case <-timer.Chan():
		// 预期行为
	default:
		t.Error("定时器应该已经触发")
	}
}

Ticker 模拟

func TestTicker(t *testing.T) {
	fakeClock := clockwork.NewFakeClock()
	ticker := fakeClock.NewTicker(time.Minute)
	
	// 推进时间并检查ticker
	fakeClock.Advance(time.Minute * 3)
	
	// 应该收到3次tick
	for i := 0; i < 3; i++ {
		select {
		case <-ticker.Chan():
			// 预期行为
		default:
			t.Errorf("应该收到第%d个tick", i+1)
		}
	}
}

在测试中使用

func TestExpiredCache(t *testing.T) {
	fakeClock := clockwork.NewFakeClock()
	cache := NewCache(fakeClock)
	
	// 添加一个1小时后过期的条目
	cache.Set("key", "value", time.Hour)
	
	// 立即检查 - 应该存在
	if !cache.Has("key") {
		t.Error("条目应该存在")
	}
	
	// 推进时间1小时1分钟
	fakeClock.Advance(time.Hour + time.Minute)
	
	// 现在条目应该过期
	if cache.Has("key") {
		t.Error("条目应该已过期")
	}
}

实际应用示例

type RateLimiter struct {
	clock    clockwork.Clock
	interval time.Duration
	lastCall time.Time
}

func NewRateLimiter(interval time.Duration, clock clockwork.Clock) *RateLimiter {
	return &RateLimiter{
		clock:    clock,
		interval: interval,
	}
}

func (r *RateLimiter) Allow() bool {
	now := r.clock.Now()
	if now.Sub(r.lastCall) >= r.interval {
		r.lastCall = now
		return true
	}
	return false
}

func TestRateLimiter(t *testing.T) {
	fakeClock := clockwork.NewFakeClock()
	limiter := NewRateLimiter(time.Second, fakeClock)
	
	// 第一次应该允许
	if !limiter.Allow() {
		t.Error("第一次调用应该允许")
	}
	
	// 立即再次调用应该拒绝
	if limiter.Allow() {
		t.Error("立即再次调用应该拒绝")
	}
	
	// 推进时间1秒后应该允许
	fakeClock.Advance(time.Second)
	if !limiter.Allow() {
		t.Error("1秒后应该允许")
	}
}

总结

Clockwork 提供了以下主要功能:

  1. 模拟时间推进
  2. 模拟定时器和ticker
  3. 设置特定时间点
  4. 完全替代标准库的time包功能

使用 Clockwork 可以让你在测试中精确控制时间因素,避免测试依赖于实际时间流逝,使测试更加可靠和快速。

回到顶部