GoLang - Go中Mocking(2)

尝试并运行测试
countdown_test.go:21: got ‘3’ want ‘3
2
1
Go!’
写足够的代码令测试通过
func Countdown(out io.Writer) {
for i := 3; i > 0; i-- {
fmt.Fprintln(out, i)
}
fmt.Fprint(out, “Go!”)
}
使用 for 循环与 i-- 反向计数,并且用 fmt.println 打印我们的数字到 out,后面跟着一个换行符。最后用 fmt.Fprint 发送 「Go!」。

重构代码
这里已经没有什么可以重构的了,只需要将变量重构为命名常量。

const finalWord = “Go!”
const countdownStart = 3
func Countdown(out io.Writer) {
for i := countdownStart; i > 0; i-- {
fmt.Fprintln(out, i)
}
fmt.Fprint(out, finalWord)
}
如果你现在运行程序,你应该可以获得想要的输出,但是向下计数的输出没有 1 秒的暂停。

Go 可以通过 time.Sleep 实现这个功能。尝试将其添加到我们的代码中。

func Countdown(out io.Writer) {
for i := countdownStart; i > 0; i-- {
time.Sleep(1 * time.Second)
fmt.Fprintln(out, i)
}

time.Sleep(1 * time.Second)
fmt.Fprint(out, finalWord)

}
如果你运行程序,它会以我们期望的方式工作。

Mocking
测试可以通过,软件按预期的工作。但是我们有一些问题:

我们的测试花费了 4 秒的时间运行

每一个关于软件开发的前沿思考性文章,都强调快速反馈循环的重要性。

缓慢的测试会破坏开发人员的生产力。

想象一下,如果需求变得更复杂,将会有更多的测试。对于每一次新的 Countdown 测试,我们是否会对被添加到测试运行中 4 秒钟感到满意呢?

我们还没有测试这个函数的一个重要属性。

我们有个 Sleeping 的注入,需要抽离出来然后我们才可以在测试中控制它。

如果我们能够 mock time.Sleep,我们可以用 依赖注入 的方式去来代替「真正的」time.Sleep,然后我们可以使用断言 监视调用。

先写测试
让我们将依赖关系定义为一个接口。这样我们就可以在 main 使用 真实的 Sleeper,并且在我们的测试中使用 spy sleeper。通过使用接口,我们的 Countdown 函数忽略了这一点,并为调用者增加了一些灵活性。

type Sleeper interface {
Sleep()
}
我做了一个设计的决定,我们的 Countdown 函数将不会负责 sleep 的时间长度。 这至少简化了我们的代码,也就是说,我们函数的使用者可以根据喜好配置休眠的时长。

现在我们需要为我们使用的测试生成它的 mock。

type SpySleeper struct {
Calls int
}

func (s *SpySleeper) Sleep() {
s.Calls++
}
监视器(spies)是一种 mock,它可以记录依赖关系是怎样被使用的。它们可以记录被传入来的参数,多少次等等。在我们的例子中,我们跟踪记录了 Sleep() 被调用了多少次,这样我们就可以在测试中检查它。

更新测试以注入对我们监视器的依赖,并断言 sleep 被调用了 4 次。

func TestCountdown(t *testing.T) {
buffer := &bytes.Buffer{}
spySleeper := &SpySleeper{}

Countdown(buffer, spySleeper)

got := buffer.String()
want := `3

2
1
Go!`

if got != want {
    t.Errorf("got %q want %q", got, want)
}

if spySleeper.Calls != 4 {
    t.Errorf("not enough calls to sleeper, want 4 got %d", spySleeper.Calls)
}

}
尝试并运行测试
too many arguments in call to Countdown
have (*bytes.Buffer, *SpySleeper)
want (io.Writer)
为测试的运行编写最少量的代码,并检查失败测试的输出
我们需要更新 Countdow 来接受我们的 Sleeper。

func Countdown(out io.Writer, sleeper Sleeper) {
for i := countdownStart; i > 0; i-- {
time.Sleep(1 * time.Second)
fmt.Fprintln(out, i)
}

time.Sleep(1 * time.Second)
fmt.Fprint(out, finalWord)

}
如果您再次尝试,你的 main 将不会出现相同编译错误的原因

./main.go:26:11: not enough arguments in call to Countdown
have (*os.File)
want (io.Writer, Sleeper)
让我们创建一个 真正的 sleeper 来实现我们需要的接口

type DefaultSleeper struct {}

func (d *DefaultSleeper) Sleep() {
time.Sleep(1 * time.Second)
}
我决定做点额外的努力,让它成为我们真正的可配置的 sleeper。但你也可以在 1 秒内毫不费力地编写它。

我们可以在实际应用中使用它,就像这样:

func main() {
sleeper := &DefaultSleeper{}
Countdown(os.Stdout, sleeper)
}
足够的代码令测试通过
现在测试正在编译但是没有通过,因为我们仍然在调用 time.Sleep 而不是依赖注入。让我们解决这个问题。

func Countdown(out io.Writer, sleeper Sleeper) {
for i := countdownStart; i > 0; i-- {
sleeper.Sleep()
fmt.Fprintln(out, i)
}

sleeper.Sleep()
fmt.Fprint(out, finalWord)

}
测试应该可以该通过,并且不再需要 4 秒。

猜你喜欢

转载自blog.csdn.net/xiabiao1974/article/details/107525894
今日推荐