是否有一种简单的方法可以在测试期间将golang全局存在于全局中。

时间:2013-09-23 22:58:43

标签: unit-testing go

我们的部分代码是时间敏感的,我们需要能够预留一些东西,然后在30-60秒内释放它,我们可以做time.Sleep(60 * time.Second)

我刚刚实现了时间界面,在测试期间使用了时间界面的存根实现,类似于this golang-nuts discussion

但是,time.Now()在多个站点中被调用,这意味着我们需要传递一个变量来跟踪我们实际睡了多少时间。

我想知道是否有另一种方法可以在全球范围内存根time.Now()。也许进行系统调用来改变系统时钟?

也许我们可以编写自己的时间包,它基本上包裹时间包但允许我们更改它?

我们目前的实施情况很好,我是初学者,我很想知道是否有其他人有其他想法?

8 个答案:

答案 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) }
  

和测试实施。


Original

在进行测试(或一般情况下)时更改系统时间是一个坏主意。 你不知道在执行测试时取决于系统时间的是什么,你不想通过花费数天的调试来找出困难的方法。不要这样做。

也无法全局影响时间包并且这样做不会 你不能用接口解决方案做任何事情。你可以写自己的时间包 它使用标准库并提供切换到模拟时间库的功能 测试你是否需要传递时间对象来解决困扰你的界面解决方案。

设计和测试代码的最佳方法可能是尽可能多地使代码无状态。 在可测试的无状态部分中分离您的功能。单独测试这些组件要容易得多。此外,较少的副作用意味着使代码同时运行更容易。

答案 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
}
  • 在struct中嵌入时间生成器
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

的工作情况

go-playground sample