Подтвердить что ты не робот

Есть ли простой способ заглушить time.Now() глобально во время теста?

Часть нашего кода чувствительна ко времени, и нам нужно иметь возможность зарезервировать что-то, а затем выпустить его через 30-60 секунд и т.д., Что мы можем просто сделать time.Sleep(60 * time.Second)

Я только что реализовал интерфейс времени и во время теста использую зашитую реализацию интерфейса времени, похожую на это обсуждение голаньянок.

Однако time.Now() вызывается в нескольких местах, что означает, что нам нужно передать переменную, чтобы отслеживать, сколько времени мы на самом деле спали.

Мне было интересно, есть ли альтернативный способ заглушить time.Now() во всем мире. Может быть, сделать системный вызов, чтобы изменить системные часы?

Может быть, мы можем написать наш собственный пакет времени, который в основном оборачивает пакет времени, но позволяет нам его менять?

Наша текущая реализация работает хорошо, я начинающий, и мне любопытно посмотреть, есть ли у кого-нибудь другие идеи?

4b9b3361

Ответ 1

Благодаря реализации пользовательского интерфейса вы уже на правильном пути. Насколько я понимаю, вы воспользовались следующим советом из опубликованной вами темы "Голангские орехи":


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

Изменение системного времени при выполнении тестов (или вообще) - плохая идея. Вы не знаете, что зависит от системного времени при выполнении тестов, и вы не хотите найти трудный путь, потратив на это дни отладки. Только не делай этого.

Также нет способа скрыть пакет времени в глобальном масштабе, и это не поможет ничего больше, что вы не могли сделать с интерфейсным решением. Вы можете написать свой пакет времени который использует стандартную библиотеку и предоставляет функцию для переключения на библиотеку фиктивного времени для тестирование, если это временный объект, который вам нужно передать с интерфейсным решением, которое вас беспокоит.

Лучший способ спроектировать и протестировать ваш код - это сделать как можно больше кода без состояния. Разделите вашу функциональность на тестируемые части без сохранения состояния. Тестирование этих компонентов по отдельности намного проще. Кроме того, меньше побочных эффектов означает, что намного проще одновременно выполнять код.

Ответ 2

Я использую пакет 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())
}

Это хорошо работает в тестах для подделки системных зависимостей и позволяет избежать злоупотребления шаблоном DI. Производственный код остается отдельным от тестового кода, и вы получаете полезный контроль над зависимостями системы.

Ответ 3

Если методов, которые вам нужно высмеять, мало, например 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

Ответ 4

Также, если вам нужно просто заглушить 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): (

Ответ 5

Из результата Google я нашел относительно простое решение: Здесь

Основная идея - использовать другой вызов функции "nowFunc", чтобы получить время. Теперь(). В основном, инициализируйте эту функцию, чтобы вернуть time.Now(). В вашем тесте инициализируйте эту функцию, чтобы вернуть фиксированное время подделки.

Ответ 6

Существует несколько способов макетирования или заглушки time.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 на golang подробно рассказывается об этом, и есть пример того, как легко проверить функцию с зависимостью от времени.