我们的部分代码是时间敏感的,我们需要能够预留一些东西,然后在30-60秒内释放它,我们可以做time.Sleep(60 * time.Second)
我刚刚实现了时间界面,在测试期间使用了时间界面的存根实现,类似于this golang-nuts discussion。
但是,time.Now()
在多个站点中被调用,这意味着我们需要传递一个变量来跟踪我们实际睡了多少时间。
我想知道是否有另一种方法可以在全球范围内存根time.Now()
。也许进行系统调用来改变系统时钟?
也许我们可以编写自己的时间包,它基本上包裹时间包但允许我们更改它?
我们目前的实施情况很好,我是初学者,我很想知道是否有其他人有其他想法?
答案 0 :(得分:38)
通过实施自定义界面,已经正确。我认为你使用了以下来自golang坚果帖子的建议:
type Clock interface {
Now() time.Time
After(d time.Duration) <-chan time.Time
}
并提供具体实施
type realClock struct{}
func (realClock) Now() time.Time { return time.Now() }
func (realClock) After(d time.Duration) <-chan time.Time { return time.After(d) }
和测试实施。
在进行测试(或一般情况下)时更改系统时间是一个坏主意。 你不知道在执行测试时取决于系统时间的是什么,你不想通过花费数天的调试来找出困难的方法。不要这样做。
也无法全局影响时间包并且这样做不会 你不能用接口解决方案做任何事情。你可以写自己的时间包 它使用标准库并提供切换到模拟时间库的功能 测试你是否需要传递时间对象来解决困扰你的界面解决方案。
设计和测试代码的最佳方法可能是尽可能多地使代码无状态。 在可测试的无状态部分中分离您的功能。单独测试这些组件要容易得多。此外,较少的副作用意味着使代码同时运行更容易。
答案 1 :(得分:19)
我使用bouk/monkey package将我的代码中的time.Now()
调用替换为假冒:
package main
import (
"fmt"
"time"
"github.com/bouk/monkey"
)
func main() {
wayback := time.Date(1974, time.May, 19, 1, 2, 3, 4, time.UTC)
patch := monkey.Patch(time.Now, func() time.Time { return wayback })
defer patch.Unpatch()
fmt.Printf("It is now %s\n", time.Now())
}
这在测试中很好地伪造了系统依赖性并避免了abused DI pattern。生产代码与测试代码分开,您可以获得对系统依赖性的有用控制。
答案 2 :(得分:10)
如果您需要模拟的方法很少,例如Now()
,您可以创建一个可以被测试覆盖的包变量:
package foo
import "time"
var Now = time.Now
// The rest of your code...which calls Now() instead of time.Now()
然后在你的测试文件中:
package foo
import (
"testing"
"time"
)
var Now = func() time.Time { return ... }
// Your tests
答案 3 :(得分:6)
此外,如果您只需要存根time.Now
,您可以将依赖项作为函数注入,例如
func moonPhase(now func() time.Time) {
if now == nil {
now = time.Now
}
// use now()...
}
// Then dependent code uses just
moonPhase(nil)
// And tests inject own version
stubNow := func() time.Time { return time.Unix(1515151515, 0) }
moonPhase(stubNow)
如果你来自动态语言背景(例如Ruby),那么这一切都有点难看:(
答案 4 :(得分:5)
我们已将为此目的在内部使用的库开源:https://github.com/cabify/timex
它提供与本地time
包相同的API,但可以在运行时从测试中替换该实现。
这是@Flimzy先前提出的解决方案的混合。
答案 5 :(得分:2)
从Google搜索结果中我找到了相对简单的解决方案:Here
基本思想是使用另一个函数调用&#34; nowFunc&#34;得到时间。现在()。 在您的main中,初始化此函数以返回time.Now()。在您的测试中,初始化此函数以返回固定的假时间。
答案 6 :(得分:0)
模拟时间或存根时间有多种方法。测试代码中的now():
func CheckEndOfMonth(now time.Time) {
...
}
CheckEndOfMonth(now func() time.Time) {
// ...
x := now()
}
type Clock interface {
Now() time.Time
}
type realClock struct {}
func (realClock) Now() time.Time { return time.Now() }
func main() {
CheckEndOfMonth(realClock{})
}
type nowFuncT func() time.Time
var nowFunc nowFuncT
func TestCheckEndOfMonth(t *Testing.T) {
nowFunc = func() time.Time {
return time.Now()
}
defer function() {
nowFunc = time.Now
}
// Test your code here
}
type TimeValidator struct {
// .. your fields
clock func() time.Time
}
func (t TimeValidator) CheckEndOfMonth() {
x := t.now()
// ...
}
func (t TimeValidator) now() time.Time {
if t.clock == nil {
return time.Now() // default implementation which fall back to standard library
}
return t.clock()
}
每个都有自己的正负号。最好的方法是将生成时间的函数与使用时间的处理部分分开。
此帖子Stubing Time in golang对此进行了详细介绍,并且有一个示例使具有时间依赖性的函数易于测试。
答案 7 :(得分:0)
我们可以存根时间。现在只需使用 go 包 "github.com/undefinedlabs/go-mpatch"。
导入 go-mpatch 包,并将下面的代码片段放在代码中需要存根的地方 time.Now()
mpatch.PatchMethod(time.Now, func() time.Time {
return time.Date(2020, 11, 01, 00, 00, 00, 0, time.UTC)
})
根据需要替换 time.Date 的值。
检查示例代码以检查 go-mpatch
的工作情况