Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to test code that uses time.Ticker?

Tags:

go

I'd like your advice on the correct way to test code that uses time.Ticker

For instance, let's say I have a countdown timer like below (just an example I thought up for the purposes of this question):

type TickFunc func(d time.Duration)

func Countdown(duration time.Duration, interval time.Duration, tickCallback TickFunc) {
    ticker := time.NewTicker(interval)
    for remaining := duration; remaining >= 0; remaining -= interval {
        tickCallback(remaining)
        <-ticker.C
    }

    ticker.Stop()
 }

http://play.golang.org/p/WJisY52a5L

If I wanted to test this, I'd want to provide a mock so that I can have tests that run quickly and predictably, so I'd need to find a way to get my mock into the Countdown function.

I can think of a few ways to do this:

Create a Ticker interface and a first class function internal to the package that I can patch for the purposes of testing: http://play.golang.org/p/oSGY75vl0U

Create a Ticker interface and pass an implementation directly to the Countdown function: http://play.golang.org/p/i67Ko5t4qk

If I do it the latter way, am I revealing too much information about how Countdown works and making it more difficult for potential clients to use this code? Instead of giving a duration and interval, they have to construct and pass in a Ticker.

I'm very interested in hearing what's the best approach when testing code like this? Or how you would change the code to preserve the behaviour, but make it more testable?

Thanks for your help!

like image 360
arun Avatar asked Feb 22 '14 14:02

arun


1 Answers

Since this is a pretty simple function, I assume you are just using this as an example of how to mock non-trivial stuff. If you actually wanted to test this code, rather than mocking up ticker, why not just use really small intervals.

IMHO the 2nd option is the better of the two, making a user call:

foo(dur, NewTicker(interval)... 

doesn't seem like much of a burden.

Also having the callback is serious code smell in Go:

func Countdown(ticker Ticker, duration time.Duration) chan time.Duration {
    remainingCh := make(chan time.Duration, 1)
    go func(ticker Ticker, dur time.Duration, remainingCh chan time.Duration) {
        for remaining := duration; remaining >= 0; remaining -= ticker.Duration() {
            remainingCh <- remaining
            ticker.Tick()
        }
        ticker.Stop()
        close(remainingCh)
    }(ticker, duration, remainingCh)
    return remainingCh
}

You could then use this code like:

func main() {
    for d := range Countdown(NewTicker(time.Second), time.Minute) {
        log.Printf("%v to go", d)
    }
}

Here it is on the playground: http://play.golang.org/p/US0psGOvvt

like image 75
voidlogic Avatar answered Oct 13 '22 18:10

voidlogic