Countdown
函数来处理这个问题,然后放入 main
程序,所以它看起来这样:buffer
不熟悉,请重新阅读前面的部分。Countdown
函数将数据写到某处,io.writer
就是作为 Go 的一个接口来抓取数据的一种方式。main
中,我们将信息发送到 os.Stdout
,所以用户可以看到 Countdown
的结果打印到终端bytes.Buffer
,所以我们的测试能够抓取到正在生成的数据./countdown_test.go:11:2: undefined: Countdown
Countdown
函数countdown_test.go:17: got '' want '3'
fmt.Fprint
传入一个 io.Writer
(例如 *bytes.Buffer
)并发送一个 string
。这个测试应该可以通过。*bytes.Buffer
可以运行,但最好使用通用接口代替。main
中。这样的话,我们就有了一些可工作的软件来确保我们的工作正在取得进展。string
的另一种方式,但是允许你放置东西例如放到新的一行,对我们的测试来说是完美的。for
循环与 i--
反向计数,并且用 fmt.println
打印我们的数字到 out
,后面跟着一个换行符。最后用 fmt.Fprint
发送 「Go!」。time.Sleep
实现这个功能。尝试将其添加到我们的代码中。Countdown
测试,我们是否会对被添加到测试运行中 4 秒钟感到满意呢?Sleep
ing 的注入,需要抽离出来然后我们才可以在测试中控制它。time.Sleep
,我们可以用 依赖注入 的方式去来代替「真正的」time.Sleep
,然后我们可以使用断言 监视调用。main
使用 真实的 Sleeper
,并且在我们的测试中使用 spy sleeper。通过使用接口,我们的 Countdown
函数忽略了这一点,并为调用者增加了一些灵活性。Countdown
函数将不会负责 sleep
的时间长度。 这至少简化了我们的代码,也就是说,我们函数的使用者可以根据喜好配置休眠的时长。Sleep()
被调用了多少次,这样我们就可以在测试中检查它。sleep
被调用了 4 次。Countdow
来接受我们的 Sleeper
。main
将不会出现相同编译错误的原因time.Sleep
而不是依赖注入。让我们解决这个问题。Countdown
应该在第一个打印之前 sleep,然后是直到最后一个前的每一个,例如:Sleep
Print N
Sleep
Print N-1
Sleep
etc
sleep
了 4 次,但是那些 sleeps
可能没按顺序发生。CountdownOperationsSpy
同时实现了 io.writer
和 Sleeper
,把每一次调用记录到 slice
。在这个测试中,我们只关心操作的顺序,所以只需要记录操作的代名词组成的列表就足够了。Sleeper
上有两个测试监视器,所以我们现在可以重构我们的测试,一个测试被打印的内容,另一个是确保我们在打印时间 sleep
。最后我们可以删除第一个监视器,因为它已经不需要了。