是否有一种简单的方法可以在测试期间将golang()全局发送到全局.

sam*_*mol 34 unit-testing go

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

我刚刚实现了时间接口,并且在测试期间使用了时间接口的存根实现,类似于这个golang-nuts讨论.

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

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

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

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

nem*_*emo 41

通过实现自定义界面,您已经采用了正确的方式.我把它从你发布的golang坚果线程中使用以下建议:


type Clock interface {
  Now() time.Time
  After(d time.Duration) <-chan time.Time
}
Run Code Online (Sandbox Code Playgroud)

并提供具体的实施

type realClock struct{}
func (realClock) Now() time.Time { return time.Now() }
func (realClock) After(d time.Duration) <-chan time.Time { return time.After(d) }
Run Code Online (Sandbox Code Playgroud)

和测试实施.


原版的

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

也没有办法在全局范围内影响时间包,这样做不会对接口解决方案做任何更多的事情.您可以编写自己的时间包,它使用标准库,并提供一个函数来切换到模拟时间库,以便测试是否需要传递时间对象以及困扰您的接口解决方案.

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

  • 我基于这个想法创建了一个包:https://github.com/101loops/clock (7认同)

All*_*uce 23

我使用bouk/monkey包来代替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())
}
Run Code Online (Sandbox Code Playgroud)

这在测试中很有效,可以伪造系统依赖性并避免滥用的DI模式.生产代码与测试代码分开,您可以获得对系统依赖性的有用控制.

  • 无论许可证如何,都不应在生产中执行此操作。 (11认同)
  • 注意:由于其许可证https://github.com/bouk/monkey/blob/master/LICENSE.md,因此您不应在产品中使用此lib (5认同)
  • 我知道这一切 - 但是“我不授予任何人出于任何目的使用此工具的权限” - 这是否意味着不要将其用于测试?这让我很困惑,为什么它会包含在一个明确发布供人们使用的包中? (3认同)
  • @AllenLuce 这就是许可证看起来如此的原因吗?似乎是一个充满敌意的许可证 (2认同)
  • 简洁的解决方案,但我建议不要这样做:该模块已被存档,并且作者在许可证文件中声明“我没有授予任何人出于任何目的使用此工具的权限。不要使用它。” 这是非常明确的。此外,该方法不是线程安全的,并且(尽管很聪明)是相当具有侵入性的。 (2认同)

Fli*_*mzy 15

如果你需要模拟的方法很少,比如Now(),你可以创建一个可以被测试覆盖的包变量:

package foo

import "time"

var Now = time.Now

// The rest of your code...which calls Now() instead of time.Now()
Run Code Online (Sandbox Code Playgroud)

然后在你的测试文件中:

package foo

import (
    "testing"
    "time"
)

var Now = func() time.Time { return ... }

// Your tests
Run Code Online (Sandbox Code Playgroud)

  • 注意:您不必将public设为public,以便在测试中可以访问它. (7认同)
  • 因为时间在不断变化,所以它必须是一个函数。 (2认同)
  • 如果您有重复的定义,它只会产生重复定义的错误。不要那样做。并尽可能避免“init”。绝对没有理由在这里使用 init 。在这种情况下,所有 `init` 所做的都是掩盖您的编码错误,并且可能会引入额外的副作用(因为您已经替换了 `Now` - 依赖于旧行为的任何内容现在可能都被破坏了)。 (2认同)

ahm*_*hmy 7

有多种方法可以在测试代码中模拟或存根 time.Now() :

  • 将时间实例传递给函数

    func CheckEndOfMonth(now time.Time) {
      ...
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 将生成器传递给函数

    CheckEndOfMonth(now func() time.Time) {
        // ...
        x := now()
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 带有接口的抽象

    type Clock interface {
        Now() time.Time
    }
    
    type realClock struct {}
    func (realClock) Now() time.Time { return time.Now() }
    
    func main() {
        CheckEndOfMonth(realClock{})
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 包级时间生成器函数

    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
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 在结构中嵌入时间生成器

    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()
    }
    
    Run Code Online (Sandbox Code Playgroud)

每个都有自己的优点和缺点。最好的方法是将生成时间的函数和使用时间的处理部分分开。

golang 中的 Stubbing Time帖子对此进行了详细介绍,并且有一个示例可以轻松测试具有时间依赖性的函数。


dol*_*nko 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)
Run Code Online (Sandbox Code Playgroud)

如果你来自动态语言背景(例如Ruby),那么这一切都有点难看:(


col*_*ega 6

我们已经开源了一个我们一直在内部用于此目的的库:https : //github.com/cabify/timex

它提供与本机time包相同的 API,但可以在运行时从测试中替换实现。

这是@Flimzy 之前提出的解决方案的混合。


Dhi*_*ani 5

这与乔纳森·霍尔的答案相同,但我添加了一个具体的例子。

概念:

  • 您可以创建一个名为的全局函数CurrentTime来包装time.now()
  • 在测试中重新分配CurrentTime函数,并使其返回所需的值。

文件main.go

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Printf("This is the current year : %d ", GetCurrentYear())
}

// 'GetCurrentYear' function uses 'CurrentTime' function internally
func GetCurrentYear() int {
    return CurrentTime().Year()
}

var CurrentTime = func() time.Time {
    return time.Now()
}
Run Code Online (Sandbox Code Playgroud)

文件main_test.go

package main

import (
    "testing"
    "time"
    . "gopkg.in/check.v1"
)

func Test(t *testing.T) { TestingT(t) }

type S struct{}

var _ = Suite(&S{})

func (s *S) TestCurrentYearShouldBeReturnedCorrectly(c *C) {
    expectedYear := 2022
    curentInstant := time.Date(expectedYear, 12, 01, 00, 00, 00, 0, time.UTC)

    // Make 'CurrentTime' return hard-coded time in tests
    CurrentTime = func() time.Time {
        return curentInstant
    }

    c.Assert(GetCurrentYear(), Equals, expectedYear)

}
Run Code Online (Sandbox Code Playgroud)

Go Playground链接。