14 KiB
Python 代码 Kata: Fizzbuzz
原文:https://www.blog.pythonlibrary.org/2019/09/18/python-code-kata-fizzbuzz/
代码形是计算机程序员练习编码的一种有趣方式。他们也经常被用来学习如何在编写代码时实现测试驱动开发(TDD)。其中一个流行的编程招式叫做 FizzBuzz 。这也是计算机程序员普遍的面试问题。
FizzBuzz 背后的概念如下:
- 写一个程序打印数字 1-100,每一行一个
- 对于每个是 3 的倍数的数字,打印“Fizz ”,而不是数字
- 对于每个是 5 的倍数的数字,打印“Buzz”而不是数字
- 对于每个都是 3 和 5 的倍数的数字,打印“FizzBuzz”而不是数字
现在你知道你需要写什么了,你可以开始了!
创建工作空间
第一步是在您的机器上创建一个工作空间或项目文件夹。例如,你可以创建一个 katas 文件夹,里面有一个 fizzbuzz 。
下一步是安装源代码控制程序。最流行的一个是 Git,但是您也可以使用像 Mercurial 这样的东西。出于本教程的目的,您将使用 Git。可以从 Git 网站获取。
如果你是 Windows 用户,现在打开一个终端或者运行 cmd.exe。然后在终端中导航到您的 fizzbuzz 文件夹。你可以使用 cd 命令来实现。进入文件夹后,运行以下命令:
git init
这将把 fizzbuzz 文件夹初始化为 Git 存储库。你在 fizzbuzz 文件夹中添加的任何文件或文件夹现在都可以添加到 Git 并进行版本控制。
嘶嘶测试
为了简单起见,您可以在 fizzbuzz 文件夹中创建您的测试文件。许多人会将他们的测试保存在名为测试或测试的子文件夹中,并告诉他们的测试运行人员将顶层文件夹添加到 sys.path 中,以便测试可以导入它。
**注意:**如果你需要温习如何使用 Python 的 unittest 库,那么你可能会发现 Python 3 测试:unittest 简介很有帮助。
继续在你的 fizzbuzz 文件夹中创建一个名为 test_fizzbuzz.py 的文件。
现在,在 Python 文件中输入以下内容:
import fizzbuzz
import unittest
class TestFizzBuzz(unittest.TestCase):
def test_multiple_of_three(self):
self.assertEqual(fizzbuzz.process(6), 'Fizz')
if __name__ == '__main__':
unittest.main()
Python 附带了内置的 unittest 库。要使用它,你需要做的就是导入它并子类化 unittest。测试用例。然后,您可以创建一系列函数来表示您想要运行的测试。
请注意,您还导入了 fizzbuzz 模块。您还没有创建那个模块,所以当您运行这个测试代码时,您将收到一个 ModuleNotFoundError 。除了导入之外,您甚至不需要添加任何代码就可以创建这个文件,并且测试会失败。但是为了完整起见,您继续断言 fizzbuzz.process(6) 返回正确的字符串。
修复方法是创建一个空的 fizzbuzz.py 文件。这只会修复 ModuleNotFoundError ,但是它将允许您运行测试并查看其输出。
您可以通过以下方式运行测试:
python test_fizzbuzz.py
输出将如下所示:
`ERROR: test_multiple_of_three (main.TestFizzBuzz)
Traceback (most recent call last): File "/Users/michael/Dropbox/code/fizzbuzz/test_fizzbuzz.py", line 7, in test_multiple_of_three self.assertEqual(fizzbuzz.process(6), 'Fizz') AttributeError: module 'fizzbuzz' has no attribute 'process'`
在 0.001 秒内完成一项测试
失败(错误=1)
所以这告诉你你的 fizzbuzz 模块缺少一个叫做进程的属性。
您可以通过在您的 fizzbuzz.py 文件中添加一个 process() 函数来解决这个问题:
def process(number):
if number % 3 == 0:
return 'Fizz'
该函数接受一个数字,并使用模数运算符将该数字除以 3,并检查是否有余数。如果没有余数,那么你知道这个数可以被 3 整除,所以你可以返回字符串“Fizz”。
现在,当您运行测试时,输出应该如下所示:
`.
Ran 1 test in 0.000s`
好的
上面第一行中的句点表示您运行了一个测试,它通过了。
让我们快速回到这里。当测试失败时,它被认为处于“红色”状态。当测试通过时,这是一个“绿色”状态。这指的是红色/绿色/重构的测试驱动开发(TDD)咒语。大多数开发人员会通过创建一个失败的测试(红色)来开始一个新项目。然后他们会编写代码使测试通过,通常是以最简单的方式(绿色)。
当您的测试为绿色时,这是提交您的测试和代码变更的好时机。这允许您拥有一段可以回滚的工作代码。现在,您可以编写一个新的测试或重构代码来使它变得更好,而不用担心您会丢失您的工作,因为现在您有一个简单的方法来回滚到代码的前一个版本。
要提交代码,可以执行以下操作:
git add fizzbuzz.py test_fizzbuzz.py git commit -m "First commit"
第一个命令将添加两个新文件。不需要提交 。pyc 文件,只是 Python 文件。有一个方便的文件叫做*。gitignore** 您可以添加到您的 Git 存储库中,您可以使用它来排除某些文件类型或文件夹,如* . pyc。Github 有一些默认的 gitignore 文件,用于各种语言,如果您想查看示例,您可以获得这些文件。
第二个命令是如何将代码提交到本地存储库。“-m”表示消息,后面是关于您正在提交的更改的描述性消息。如果你也想把你的修改保存到 Github(这对于备份来说很好),你应该看看这篇文章。
现在我们准备编写另一个测试了!
嗡嗡声测试
你可以写的第二个测试可以是 5 的倍数。要添加一个新的测试,您可以在 TestFizzBuzz 类中创建另一个方法:
import fizzbuzz
import unittest
class TestFizzBuzz(unittest.TestCase):
def test_multiple_of_three(self):
self.assertEqual(fizzbuzz.process(6), 'Fizz')
def test_multiple_of_five(self):
self.assertEqual(fizzbuzz.process(20), 'Buzz')
if __name__ == '__main__':
unittest.main()
这一次,您希望使用只能被 5 整除的数字。当您调用 fizzbuzz.process() 时,应该会得到“buzz”的返回。但是,当您运行测试时,您将收到以下内容:
`F.
FAIL: test_multiple_of_five (main.TestFizzBuzz)
Traceback (most recent call last): File "test_fizzbuzz.py", line 10, in test_multiple_of_five self.assertEqual(fizzbuzz.process(20), 'Buzz') AssertionError: None != 'Buzz'`
在 0.000 秒内运行 2 次测试
失败(失败次数=1)
哎呀!现在,您的代码使用模数运算符来检查除以 3 后的余数。如果数字 20 有余数,这个语句就不会运行。函数的默认返回值是 None ,所以这就是为什么你会得到上面的失败。
继续将 process() 函数更新如下:
def process(number):
if number % 3 == 0:
return 'Fizz'
elif number % 5 == 0:
return 'Buzz'
现在你可以用 3 和 5 来检查余数。当您这次运行测试时,输出应该如下所示:
`..
Ran 2 tests in 0.000s`
好的
耶!您的测试通过,现在是绿色的!这意味着您可以将这些更改提交到您的 Git 存储库中。
现在你已经准备好为 FizzBuzz 添加一个测试了!
嘶嘶声测试
你能写的下一个测试将是当你想要得到“FizzBuzz”的时候。你可能还记得,只要这个数能被 3 和 5 整除,你就会得到 FizzBuzz。继续添加第三个测试:
import fizzbuzz
import unittest
class TestFizzBuzz(unittest.TestCase):
def test_multiple_of_three(self):
self.assertEqual(fizzbuzz.process(6), 'Fizz')
def test_multiple_of_five(self):
self.assertEqual(fizzbuzz.process(20), 'Buzz')
def test_fizzbuzz(self):
self.assertEqual(fizzbuzz.process(15), 'FizzBuzz')
if __name__ == '__main__':
unittest.main()
对于这个测试, test_fizzbuzz ,您要求您的程序处理数字 15。这应该还不能正常工作,但是继续运行测试代码来检查:
`F..
FAIL: test_fizzbuzz (main.TestFizzBuzz)
Traceback (most recent call last): File "test_fizzbuzz.py", line 13, in test_fizzbuzz self.assertEqual(fizzbuzz.process(15), 'FizzBuzz') AssertionError: 'Fizz' != 'FizzBuzz'`
在 0.000 秒内运行了 3 次测试
失败(失败次数=1)
进行了三次测试,只有一次失败。你现在回到红色。这次的错误是**‘嘶嘶’!= 'FizzBuzz'** 而不是拿 None 和 FizzBuzz 比较。原因是你的代码检查 15 是否能被 3 整除,所以它返回“Fizz”。
因为这不是您想要发生的,所以您将需要更新您的代码,在检查 3:
def process(number):
if number % 3 == 0 and number % 5 == 0:
return 'FizzBuzz'
elif number % 3 == 0:
return 'Fizz'
elif number % 5 == 0:
return 'Buzz'
在这里,首先对 3 和 5 进行整除检查。然后像以前一样检查另外两个。
现在,如果您运行您的测试,您应该得到以下输出:
`...
Ran 3 tests in 0.000s`
好的
到目前为止一切顺利。然而,你没有返回不能被 3 或 5 整除的数字的代码。该进行另一项测试了!
最终测试
您的代码需要做的最后一件事是,当该数除以 3 和 5 后还有余数时,返回该数。让我们用几种不同的方法来测试它:
import fizzbuzz
import unittest
class TestFizzBuzz(unittest.TestCase):
def test_multiple_of_three(self):
self.assertEqual(fizzbuzz.process(6), 'Fizz')
def test_multiple_of_five(self):
self.assertEqual(fizzbuzz.process(20), 'Buzz')
def test_fizzbuzz(self):
self.assertEqual(fizzbuzz.process(15), 'FizzBuzz')
def test_regular_numbers(self):
self.assertEqual(fizzbuzz.process(2), 2)
self.assertEqual(fizzbuzz.process(98), 98)
if __name__ == '__main__':
unittest.main()
对于这个测试,您将使用 test_regular_numbers() 测试来测试正常数字 2 和 98。这些数字在被 3 或 5 除时总会有余数,所以它们应该被返回。
当您现在运行测试时,您应该得到类似这样的结果:
`...F
FAIL: test_regular_numbers (main.TestFizzBuzz)
Traceback (most recent call last): File "test_fizzbuzz.py", line 16, in test_regular_numbers self.assertEqual(fizzbuzz.process(2), 2) AssertionError: None != 2`
在 0.000 秒内运行了 4 次测试
失败(失败次数=1)
这一次,您又回到了将 None 与 number 进行比较,这就是您可能怀疑的输出。
继续更新**进程()**函数,如下所示:
def process(number):
if number % 3 == 0 and number % 5 == 0:
return 'FizzBuzz'
elif number % 3 == 0:
return 'Fizz'
elif number % 5 == 0:
return 'Buzz'
else:
return number
那很容易!此时您需要做的就是添加一个返回数字的 else 语句。
现在,当您运行测试时,它们应该都通过了:
`....
Ran 4 tests in 0.000s`
好的
干得好!现在你的代码工作了。您可以通过将以下内容添加到您的 fizzbuzz.py 模块来验证它是否适用于 1-100 的所有数字:
if __name__ == '__main__':
for i in range(1, 101):
print(process(i))
现在,当您自己使用 python fizzbuzz.py 运行 fizzbuzz 时,您应该会看到本教程开头指定的适当输出。
这是提交您的代码并将其推送到云的好时机。
包扎
现在你知道了使用测试驱动开发来驱动你解决编码问题的基础。Python 的 unittest 模块拥有比这篇简短教程中所涵盖的更多类型的断言和功能。您还可以修改本教程以使用 pytest,这是另一个流行的第三方 Python 包,您可以用它来代替 Python 自己的 unittest 模块。
进行这些测试的好处是,现在您可以重构代码,并通过运行测试来验证您没有破坏任何东西。这也允许您在不破坏现有功能的情况下更容易地添加新功能。只要确保在添加更多特性时添加更多测试。
相关阅读
- Python 的单元测试模块文档
- pytest 网站
- Python 使用 doctest 进行测试
- Python 3 测试:单元测试简介
- python 102:TDD 和单元测试简介