英文:
Correct way to test code that uses time.Ticker?
问题
我很乐意为你提供关于如何测试使用time.Ticker的代码的正确方法的建议。
例如,假设我有一个倒计时器,如下所示(这只是我为了提问而想出的一个示例):
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()
}
如果我想要对此进行测试,我希望提供一个模拟对象,以便能够快速且可预测地运行测试,因此我需要找到一种方法将我的模拟对象传递给Countdown函数。
我可以想到几种方法来实现这一点:
如果我选择后一种方式,是否会透露太多关于Countdown如何工作的信息,从而使潜在的客户在使用此代码时更加困难?因为他们需要构造并传递一个Ticker对象,而不是提供持续时间和间隔。
我非常想听听在测试这样的代码时,最佳的方法是什么?或者你会如何更改代码以保留其行为,同时使其更易于测试?
谢谢你的帮助!
英文:
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!
答案1
得分: 4
由于这是一个相当简单的函数,我假设你只是将其作为模拟非平凡内容的示例。如果你真的想测试这段代码,而不是模拟 ticker,为什么不使用非常小的时间间隔呢?
在我看来,第二个选项是两者中更好的选择,让用户调用:
foo(dur, NewTicker(interval)...
似乎不是太麻烦。
此外,在 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
}
然后你可以像这样使用这段代码:
func main() {
for d := range Countdown(NewTicker(time.Second), time.Minute) {
log.Printf("%v to go", d)
}
}
这是在 playground 上的链接:http://play.golang.org/p/US0psGOvvt
英文:
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
答案2
得分: 0
这不回答如何注入模拟部分的问题,但看起来你努力过头了。
如果示例代表了你实际测试的内容,那么只需使用小的数字即可。
http://play.golang.org/p/b_1kqyIu-u
Countdown(5, 1, func(d time.Duration) {
log.Printf("%v to go", d)
})
现在,如果你正在测试调用 Countdown 的代码(而不是测试 Countdown 本身),那么我可能会创建一个标志,你可以为你的模块设置该标志,以便在相同的调用计数下尽可能快地缩放数字。
http://play.golang.org/p/KqCGnaR3vc
if testMode {
duration = duration/interval
interval = 1
}
英文:
This doesn't answer the how to inject the mock part, but it seems like you are trying too hard.
if the example is representative of what you actually are testing, then just use small numbers.
http://play.golang.org/p/b_1kqyIu-u
Countdown(5, 1, func(d time.Duration) {
log.Printf("%v to go", d)
})
Now, if you are testing code that calls Countdown (rather than testing Countdown), then I'd probably just create a flag you can set for your module that scales the numbers to be as fast possible with the same invocation count.
http://play.golang.org/p/KqCGnaR3vc
if testMode {
duration = duration/interval
interval = 1
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论