Mocking
你可以在这里找到本章的所有代码
现在需要你写一个程序,从 3 开始依次向下,当到 0 时打印 「GO!」 并退出,要求每次打印从新的一行开始且打印间隔一秒的停顿。
3
2
1
Go!
我们将通过编写一个 Countdown 函数来处理这个问题,然后放入 main 程序,所以它看起来这样:
package main
func main() {
Countdown()
}
虽然这是一个非常简单的程序,但要完全测试它,我们需要像往常一样采用迭代的、测试驱动的方法。
所谓迭代是指:确保我们采取最小的步骤让软件可用。
我们不想花太多时间写那些在被攻击后理论上还能运行的代码,因为这经常导致开发人员陷入开发的无底深渊。尽你所能拆分需求是一项很重要的技能,这样你就能拥有可以工作的软件。
下面是我们如何划分工作和迭代的方法:
打印 3
打印 3 到 Go!
在每行中间等待一秒
先写测试
我们的软件需要将结果打印到标准输出界面。在 DI(依赖注入) 的部分,我们已经看到如何使用 DI 进行方便的测试。
func TestCountdown(t *testing.T) {
buffer := &bytes.Buffer{}
Countdown(buffer)
got := buffer.String()
want := "3"
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
如果你对 buffer 不熟悉,请重新阅读前面的部分。
我们清楚,我们的目的是让 Countdown 函数将数据写到某处,io.writer 就是作为 Go 的一个接口来抓取数据的一种方式。
在 main 中,我们将信息发送到 os.Stdout,所以用户可以看到 Countdown 的结果打印到终端
在测试中,我们将发送到 bytes.Buffer,所以我们的测试能够抓取到正在生成的数据
尝试并运行测试
./countdown_test.go:11:2: undefined: Countdown
为测试的运行编写最少量的代码,并检查失败测试的输出
定义 Countdown 函数
func Countdown() {}
再次尝试运行
./countdown_test.go:11:11: too many arguments in call to Countdown
have (*bytes.Buffer)
want ()
编译器正在告诉你函数的问题,所以更正它
func Countdown(out *bytes.Buffer) {}
countdown_test.go:17: got ‘’ want ‘3’
这样结果就完美了!
编写足够的代码使程序通过
func Countdown(out *bytes.Buffer) {
fmt.Fprint(out, “3”)
}
我们正在使用 fmt.Fprint 传入一个 io.Writer(例如 *bytes.Buffer)并发送一个 string。这个测试应该可以通过。
重构代码
虽然我们都知道 *bytes.Buffer 可以运行,但最好使用通用接口代替。
func Countdown(out io.Writer) {
fmt.Fprint(out, “3”)
}
重新运行测试他们应该就可以通过了。
为了完成任务,现在让我们将函数应用到 main 中。这样的话,我们就有了一些可工作的软件来确保我们的工作正在取得进展。
package main
import (
“fmt”
“io”
“os”
)
func Countdown(out io.Writer) {
fmt.Fprint(out, “3”)
}
func main() {
Countdown(os.Stdout)
}
尝试运行程序,这些成果会让你感到神奇。
当然,这仍然看起来很简单,但是我建议任何项目都使用这种方法。在测试的支持下,将功能切分成小的功能点,并使其首尾相连顺利的运行。
接下来我们可以让它打印 2,1 然后输出「Go!」。
先写测试
通过花费一些时间让整个流程正确执行,我们就可以安全且轻松的迭代我们的解决方案。我们将不再需要停止并重新运行程序,要对它的工作充满信心因为所有的逻辑都被测试过了。
func TestCountdown(t *testing.T) {
buffer := &bytes.Buffer{}
Countdown(buffer)
got := buffer.String()
want := `3
2
1
Go!`
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
反引号语法是创建 string 的另一种方式,但是允许你放置东西例如放到新的一行,对我们的测试来说是完美的。