FizzBuzz 问题
实现 FizzBuzz 问题,以熟悉 TDD 方法的基本流程。
我们将开始使用 TDD 方法进行练习。在这一系列课程中,我们将通过该技术来实现一个编程练习(kata),所采用的示例 kata 是 FizzBuzz。
在开始之前,让我们先回顾一下它的规则:FizzBuzz 函数接收一个整数作为参数,并返回一个字符串。返回值根据以下条件决定:
- 如果数字能被 3 整除,返回
Fizz。 - 如果数字能被 5 整除,返回
Buzz。 - 如果数字既能被 3 又能被 5 整除,返回
FizzBuzz。 - 如果数字既不能被 3 也不能被 5 整除,返回该数字的字符串表示形式。
在这个 kata 中,我们可以明确地看出有四个需要实现的功能点(或需求)。通过使用 TDD 方法逐一实现这些功能,我们将熟悉这种开发方式,并为应对更复杂的项目做好准备。
基本场景
TDD 方法要求我们首先思考问题中最基础的部分。解决完这部分后,再逐步添加其他功能。每实现一个功能,都必须完整经历 TDD 的三个阶段:红(Red)、绿(Green)和重构(Refactor)。
红色阶段
让我们看看我们的初始状态是什么样的:
No configuration found for ID: 63fac1fd53f840439ff2fbfe9e7a9f9e56d51c08cc922b253e0cc1013892b677
值得注意的是,我们已经编写了第四个需求的最基本场景,即当传入的数字既不能被 3 也不能被 5 整除时的情况。让我们仔细分析这个解决方案。整个项目只包含两个文件:
fizzbuzz.go文件将存放本次 kata 的核心逻辑。fizzbuzz_test.go文件将存放针对FizzBuzz函数的测试代码。
此外,在 TestFizzBuzz 函数中,我们遵循了 AAA(Arrange、Act、Assert)模式进行组织。当我们使用 go test -v -cover 命令运行测试时,出现了编译错误:./fizzbuzz_test.go:10:9: undefined: FizzBuzz。这是因为我们尚未在 fizzbuzz.go 文件中定义 FizzBuzz 函数。
注意:从本课起,我们始终使用命令
go test -v -cover。这样可以获取详细的输出信息,并查看实际的代码覆盖率。
TDD 强调在编写任何生产代码之前先写测试,因此出现编译失败是预期中的正常现象。
绿色阶段
我们仍处于 TDD 循环的红色阶段。此时,我们需要采取最小的一步操作,使程序从“红”进入“绿”。请看下面的代码片段:
No configuration found for ID: 987a7882ce8f26dce2e67c805787c9c645aedac0cc7fa3c88722b0e3b7e82865
我们在 fizzbuzz.go 文件中定义了 FizzBuzz 函数。这解决了控制台中的编译错误。我们引入了一个最基础的实现,即返回字符串类型的默认空值。虽然编译不再报错,但如果我们再次用相同的命令运行测试,会得到 fizzbuzz_test.go:14: expected "1" but got "" 的结果。这意味着尽管我们改进了实现,仍然处于“红”阶段。
现在,让我们看看如何真正走出“红”阶段:
No configuration found for ID: 94778925b58c43b073e3bb84712cef15310067f863fea01a26d853c7789748c5
注意:此时我们可能会想直接返回
input变量的字符串格式。但这会违反 TDD 的原则。
只需简单地返回字符串 "1",我们就能成功退出“红”阶段,进入“绿”阶段。此时再次运行测试,结果为 PASS,且代码覆盖率达到 100%。
重构阶段
此阶段用于对测试代码和生产代码进行重构。请记住,在这个阶段允许对代码进行重构,因为此时已有测试保障了代码的正确性。每次重构后,都需要重新运行所有测试,以确保没有引入回归问题。如果无需重构,此阶段可以跳过。
让我们来看看如何对代码进行重构:
No configuration found for ID: 894269a46e64492d24b5adacf4828b64b8886ea723ef1ce296eb01645224bdb2
重构仅作用于 TestFizzBuzz 函数,特别是其中的“断言”部分:
- 我们移除了断言中的魔法字符串
"1",改用strconv.Itoa函数将整数转换为字符串。 - 引入了包
"github.com/stretchr/testify/assert",使断言更加简洁易读。
为了验证重构是否成功,我们再次运行测试,结果与之前一致:PASS,代码覆盖率仍为 100%。